Skip to content

Commit

Permalink
feat(PageError): add component for pretty display error of page [YTFR…
Browse files Browse the repository at this point in the history
…ONT-4049]
  • Loading branch information
pabolkovdn committed Nov 11, 2024
1 parent 7337a64 commit 40a355b
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 25 deletions.
Binary file added packages/ui/src/ui/assets/img/empty-search.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ui/src/ui/assets/img/forbidden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion packages/ui/src/ui/components/ErrorDetails/ErrorDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ export default class ErrorDetails extends Component {
return (
<span className={b('message')}>
{text}
{code !== undefined && <React.Fragment>[{ypath.getValue(code)}]</React.Fragment>}
{code !== undefined && (
<>
&#32;<span className={b('error-code')}>[{ypath.getValue(code)}]</span>
</>
)}
</span>
);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/ui/components/ErrorDetails/ErrorDetails.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
margin-bottom: 0;
}

&__error-code {
white-space: nowrap;
}

&__tabs {
margin: 10px 0 20px;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {Component} from 'react';

import {Toaster} from '@gravity-ui/uikit';
import Error from '../../components/Error/Error';
import {PageError} from '../../components/PageError';
import hammer from '../../common/hammer';

import {showErrorPopup} from '../../utils/utils';
Expand Down Expand Up @@ -44,7 +44,7 @@ export default class LoadDataHandler extends Component<LoadDataHandlerProps> {
const initialLoading = !loaded;

if (error && (alwaysShowError || initialLoading)) {
return <Error error={errorData} />;
return <PageError error={errorData} title={errorData?.message ?? 'Error'} />;
}

return children;
Expand Down
99 changes: 99 additions & 0 deletions packages/ui/src/ui/components/PageError/PageError.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
.page-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
min-width: 100%;
min-height: 100%;

&__content {
display: flex;
flex-direction: row;
align-items: stretch;
gap: 30px;
max-width: 90vw;
word-break: break-word;
}

&__icon-container {
flex: 0;
display: flex;
align-items: center;
justify-content: center;
}

&__icon {
position: sticky;
top: calc(var(--app-header-height, 0) + 30px);
bottom: 30px;
}

&__body {
flex: auto;
display: flex;
flex-direction: column;

color: var(--g-color-text-complementary);
font-family: var(--g-text-body-font-family);
font-size: var(--g-text-subheader-1-font-size);
line-height: var(--g-text-subheader-1-line-height);
font-weight: 500;
}

&__title {
font-family: var(--g-text-body-font-family);
font-size: var(--g-text-subheader-3-font-size);
line-height: var(--g-text-subheader-3-line-height);
font-weight: 500;

margin-bottom: 12px;
}

&__details {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;

.elements-error-details__tabs {
margin: 0;
}

.elements-error-details__details {
margin-top: 4px;
padding: 8px;
border-radius: 8px;
background: var(--g-color-base-generic);
display: flex;
flex-direction: column;
gap: 10px;
}

.elements-error-details__errors,
.elements-error-details__inner-errors {
margin-bottom: 4px;
}

.elements-error-details__message {
word-wrap: break-word;
word-break: break-all;
}

.unipika-wrapper {
background: none;
padding: 0;
}

& > .elements-error-details > .elements-error-details__inner-errors {
padding-left: 0;
}
}

&__footer {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 8px;
}
}
66 changes: 66 additions & 0 deletions packages/ui/src/ui/components/PageError/PageError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import cn from 'bem-cn-lite';

import ErrorDetails from '../ErrorDetails/ErrorDetails';
import {YTError} from '../../../@types/types';

import './PageError.scss';

const block = cn('page-error');

export enum PageErrorType {
NotFound = 'not-found',
PermissionsDenied = 'permissions-denied',
}

export interface PageErrorProps {
type?: PageErrorType;
// todo: Concrete type of error is unknown
error?: YTError | object;
title: string | React.ReactNode;
footer?: React.ReactNode;
// todo: Concrete type of settings is unknown
settings?: object;
maxCollapsedDepth?: number;
}

export const PageError: React.FC<PageErrorProps> = ({
type = 'not-found',
error,
title,
footer,
settings,
maxCollapsedDepth,
}) => {
const icon = React.useMemo(() => {
switch (type) {
case PageErrorType.NotFound:
return require('../../assets/img/empty-search.png');
case PageErrorType.PermissionsDenied:
return require('../../assets/img/forbidden.png');
}
}, [type]);

const errorDetailsBlock = error ? (
<div className={block('details')}>
<ErrorDetails error={error} settings={settings} maxCollapsedDepth={maxCollapsedDepth} />
</div>
) : null;

const footerBlock = footer ? <div className={block('footer')}>{footer}</div> : null;

return (
<div className={block()}>
<div className={block('content')}>
<div className={block('icon-container')}>
<img src={icon} width={150} height={150} alt={type} className={block('icon')} />
</div>
<div className={block('body')}>
<div className={block('title')}>{title}</div>
{errorDetailsBlock}
{footerBlock}
</div>
</div>
</div>
);
};
42 changes: 42 additions & 0 deletions packages/ui/src/ui/components/PageError/error_templates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import {WordWrapPath} from '../WordWraps';

export interface TitlePathNotFoundProps {
path: string;
}

export const TitlePathNotFound: React.FC<TitlePathNotFoundProps> = ({path}): React.ReactNode => {
return (
<>
Path “<WordWrapPath path={path} />” does not exist
</>
);
};

export interface TitlePermissionsDeniedProps {
path: string;
types: string | [string, ...string[]];
user: string;
}

export const TitlePermissionsDenied: React.FC<TitlePermissionsDeniedProps> = ({
path,
types,
user,
}) => {
const typesList = (Array.isArray(types) ? types : [types]).map((type) => `“${type}”`);
const lastType = typesList.pop();
let typesValue = typesList.join(', ');

if (lastType && typesList.length > 0) {
typesValue += ' and ';
}

typesValue += lastType;

return (
<>
User “{user}” does not have {typesValue} access to node “<WordWrapPath path={path} />
</>
);
};
2 changes: 2 additions & 0 deletions packages/ui/src/ui/components/PageError/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './error_templates';
export * from './PageError';
24 changes: 24 additions & 0 deletions packages/ui/src/ui/components/WordWraps/WordWrapPath.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, {useMemo} from 'react';

const tokenRegex =
/(\/{1,2}@[^/]+?[^/.,_-]+)|(\/{1,2}[^/.,_-]+)|([.,_-]{1,3}[^/.,_-]+)|([.,_-]{1,3})/g;

export interface WordWrapPathProps {
path: string;
}

export const WordWrapPath: React.FC<WordWrapPathProps> = ({path}) => {
return useMemo(() => {
const tokens = path.match(tokenRegex)?.reduce<React.ReactNode[]>((list, part, index) => {
if (index > 0) {
list.push(<wbr />);
}

list.push(part);

return list;
}, []);

return <>{tokens}</>;
}, [path]);
};
1 change: 1 addition & 0 deletions packages/ui/src/ui/components/WordWraps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {WordWrapPath, type WordWrapPathProps} from './WordWrapPath';
13 changes: 13 additions & 0 deletions packages/ui/src/ui/pages/navigation/Navigation/Navigation.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
.navigation {
min-width: 1200px;
min-height: 100%;

&__sticky-container {
display: flex;
flex-direction: column;
min-height: 100%;
}

&__main {
display: flex;
flex-direction: column;
flex: 1;
}

&__error-action-button {
margin-top: 20px;
Expand Down
Loading

0 comments on commit 40a355b

Please sign in to comment.