Skip to content

Commit 2e6adf9

Browse files
authored
feat: configure access errors (#2855)
1 parent e7561d7 commit 2e6adf9

File tree

10 files changed

+206
-134
lines changed

10 files changed

+206
-134
lines changed

src/components/EmptyState/EmptyState.scss

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010

1111
&__wrapper {
1212
display: grid;
13-
grid-template-areas:
14-
'image title'
15-
'image description'
16-
'image actions';
13+
grid-template-areas: 'image content';
1714

1815
&_size_xs {
1916
width: 321px;
@@ -28,8 +25,8 @@
2825
}
2926

3027
&_size_m {
31-
width: 800px;
32-
height: 240px;
28+
width: 600px;
29+
height: 230px;
3330
}
3431

3532
&_position_center {
@@ -46,7 +43,7 @@
4643
grid-area: image;
4744
justify-self: end;
4845

49-
margin-right: 60px;
46+
margin-right: var(--g-spacing-10);
5047

5148
color: var(--g-color-base-info-light-hover);
5249

@@ -56,9 +53,6 @@
5653
}
5754

5855
&__title {
59-
align-self: center;
60-
grid-area: title;
61-
6256
font-weight: 500;
6357

6458
&_size_s {
@@ -71,16 +65,11 @@
7165
}
7266

7367
&__description {
74-
grid-area: description;
75-
7668
@include mixins.body-2-typography();
7769
}
7870

79-
&__actions {
80-
grid-area: actions;
81-
82-
& > * {
83-
margin-right: 8px;
84-
}
71+
&__content {
72+
align-self: center;
73+
grid-area: content;
8574
}
8675
}

src/components/EmptyState/EmptyState.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Icon} from '@gravity-ui/uikit';
1+
import {Flex, Icon, Text} from '@gravity-ui/uikit';
22

33
import {cn} from '../../utils/cn';
44

@@ -8,20 +8,22 @@ import './EmptyState.scss';
88

99
const block = cn('empty-state');
1010

11-
const sizes = {
11+
export const EMPTY_STATE_SIZES = {
1212
xs: 100,
1313
s: 150,
14-
m: 250,
14+
m: 230,
1515
l: 350,
1616
};
1717

1818
export interface EmptyStateProps {
19-
title: string;
19+
title: React.ReactNode;
2020
image?: React.ReactNode;
2121
description?: React.ReactNode;
2222
actions?: React.ReactNode[];
23-
size?: keyof typeof sizes;
23+
size?: keyof typeof EMPTY_STATE_SIZES;
2424
position?: 'left' | 'center';
25+
pageTitle?: string;
26+
className?: string;
2527
}
2628

2729
export const EmptyState = ({
@@ -31,21 +33,33 @@ export const EmptyState = ({
3133
actions,
3234
size = 'm',
3335
position = 'center',
36+
pageTitle,
37+
className,
3438
}: EmptyStateProps) => {
3539
return (
36-
<div className={block({size})}>
40+
<div className={block({size}, className)}>
41+
{pageTitle ? <Text variant="header-1">{pageTitle}</Text> : null}
3742
<div className={block('wrapper', {size, position})}>
3843
<div className={block('image')}>
3944
{image ? (
4045
image
4146
) : (
42-
<Icon data={emptyStateIcon} width={sizes[size]} height={sizes[size]} />
47+
<Icon
48+
data={emptyStateIcon}
49+
width={EMPTY_STATE_SIZES[size]}
50+
height={EMPTY_STATE_SIZES[size]}
51+
/>
4352
)}
4453
</div>
45-
46-
<div className={block('title', {size})}>{title}</div>
47-
<div className={block('description')}>{description}</div>
48-
<div className={block('actions')}>{actions}</div>
54+
<Flex gap={5} className={block('content')} direction="column">
55+
<Flex gap={3} direction="column">
56+
<div className={block('title', {size})}>{title}</div>
57+
{description ? (
58+
<div className={block('description')}>{description}</div>
59+
) : null}
60+
</Flex>
61+
{actions ? <Flex gap={2}>{actions}</Flex> : null}
62+
</Flex>
4963
</div>
5064
</div>
5165
);

src/components/Errors/403/AccessDenied.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
import {EmptyState} from '../../EmptyState';
1+
import {EMPTY_STATE_SIZES, EmptyState} from '../../EmptyState';
22
import type {EmptyStateProps} from '../../EmptyState';
33
import {Illustration} from '../../Illustration';
44
import i18n from '../i18n';
55

6-
interface AccessDeniedProps extends Omit<EmptyStateProps, 'image' | 'title' | 'description'> {
7-
title?: string;
8-
description?: string;
6+
interface AccessDeniedProps extends Omit<EmptyStateProps, 'title'> {
7+
title?: React.ReactNode;
98
}
109

11-
export const AccessDenied = ({title, description, ...restProps}: AccessDeniedProps) => {
10+
export const AccessDenied = ({
11+
title,
12+
description,
13+
image,
14+
size = 'm',
15+
...restProps
16+
}: AccessDeniedProps) => {
1217
return (
1318
<EmptyState
14-
image={<Illustration name="403" />}
19+
image={image || <Illustration name="403" width={EMPTY_STATE_SIZES[size]} />}
1520
title={title || i18n('403.title')}
1621
description={description || i18n('403.description')}
1722
{...restProps}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.ydb-page-error {
2+
display: grid;
3+
align-items: center;
4+
grid-template-rows: min-content auto;
5+
6+
height: 100%;
7+
}

src/components/Errors/PageError/PageError.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,59 @@
11
import React from 'react';
22

3+
import {cn} from '../../../utils/cn';
34
import {isAccessError, isRedirectToAuth} from '../../../utils/response';
45
import type {EmptyStateProps} from '../../EmptyState';
5-
import {EmptyState} from '../../EmptyState';
6+
import {EMPTY_STATE_SIZES, EmptyState} from '../../EmptyState';
67
import {Illustration} from '../../Illustration';
78
import {AccessDenied} from '../403';
89
import {ResponseError} from '../ResponseError';
910
import i18n from '../i18n';
1011

11-
interface PageErrorProps extends Omit<EmptyStateProps, 'image' | 'title' | 'description'> {
12-
title?: string;
13-
description?: string;
12+
import './PageError.scss';
13+
14+
const b = cn('ydb-page-error');
15+
16+
interface PageErrorProps extends Omit<EmptyStateProps, 'image' | 'title'> {
17+
title?: React.ReactNode;
1418
error: unknown;
1519
children?: React.ReactNode;
20+
errorPageTitle?: string;
1621
}
1722

18-
export function PageError({title, description, error, children, ...restProps}: PageErrorProps) {
23+
export function PageError({
24+
title,
25+
description,
26+
error,
27+
children,
28+
size = 'm',
29+
errorPageTitle,
30+
...restProps
31+
}: PageErrorProps) {
1932
if (isRedirectToAuth(error)) {
2033
// Do not show an error, because we redirect to auth anyway.
2134
return null;
2235
}
2336

2437
if (isAccessError(error)) {
25-
return <AccessDenied title={title} description={description} {...restProps} />;
38+
return (
39+
<AccessDenied
40+
title={title}
41+
description={description}
42+
{...restProps}
43+
pageTitle={errorPageTitle}
44+
className={b()}
45+
/>
46+
);
2647
}
2748

2849
if (error || description) {
2950
return (
3051
<EmptyState
31-
image={<Illustration name="error" />}
52+
image={<Illustration name="error" width={EMPTY_STATE_SIZES[size]} />}
3253
title={title || i18n('error.title')}
3354
description={error ? <ResponseError error={error} /> : description}
55+
pageTitle={errorPageTitle}
56+
className={b()}
3457
{...restProps}
3558
/>
3659
);

src/containers/App/Content.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
useMetaCapabilitiesQuery,
2121
} from '../../store/reducers/capabilities/hooks';
2222
import {nodesListApi} from '../../store/reducers/nodesList';
23+
import {uiFactory} from '../../uiFactory/uiFactory';
2324
import {cn} from '../../utils/cn';
2425
import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery';
2526
import {lazyComponent} from '../../utils/lazyComponent';
@@ -28,6 +29,7 @@ import Authentication from '../Authentication/Authentication';
2829
import {getClusterPath} from '../Cluster/utils';
2930
import Header from '../Header/Header';
3031

32+
import {useAppTitle} from './AppTitleContext';
3133
import {
3234
ClusterSlot,
3335
ClustersSlot,
@@ -192,10 +194,17 @@ function DataWrapper({children}: {children: React.ReactNode}) {
192194
function GetUser({children}: {children: React.ReactNode}) {
193195
const database = useDatabaseFromQuery();
194196
const {isLoading, error} = authenticationApi.useWhoamiQuery({database});
197+
const {appTitle} = useAppTitle();
195198

196199
return (
197200
<LoaderWrapper loading={isLoading} size="l">
198-
<PageError error={error}>{children}</PageError>
201+
<PageError
202+
error={error}
203+
{...uiFactory.clusterOrDatabaseAccessError}
204+
errorPageTitle={appTitle}
205+
>
206+
{children}
207+
</PageError>
199208
</LoaderWrapper>
200209
);
201210
}

0 commit comments

Comments
 (0)