@stack-spot/portal-layout 0.0.48 → 0.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Layout.d.ts +2 -2
- package/dist/Layout.js +1 -1
- package/dist/LayoutOverlayManager.js +6 -6
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/components/Dialog.d.ts +1 -1
- package/dist/components/Dialog.js +1 -1
- package/dist/components/Header.d.ts +1 -1
- package/dist/components/Header.js +1 -1
- package/dist/components/OverlayContent.d.ts +1 -1
- package/dist/components/OverlayContent.js +20 -20
- package/dist/components/PortalSwitcher.d.ts +1 -1
- package/dist/components/PortalSwitcher.js +54 -54
- package/dist/components/SelectionList.d.ts +1 -1
- package/dist/components/SelectionList.js +54 -54
- package/dist/components/SelectionList.js.map +1 -1
- package/dist/components/Toaster.d.ts +1 -1
- package/dist/components/Toaster.js +1 -1
- package/dist/components/UserMenu.d.ts +1 -1
- package/dist/components/UserMenu.js +42 -42
- package/dist/components/UserMenu.js.map +1 -1
- package/dist/components/error/ErrorBoundary.d.ts +1 -1
- package/dist/components/error/ErrorBoundary.js +1 -1
- package/dist/components/error/ErrorFeedback.d.ts +1 -1
- package/dist/components/error/ErrorFeedback.js +1 -1
- package/dist/components/error/SilentErrorBoundary.d.ts +1 -1
- package/dist/components/error/SilentErrorBoundary.js +1 -1
- package/dist/components/menu/MenuContent.d.ts +12 -10
- package/dist/components/menu/MenuContent.d.ts.map +1 -1
- package/dist/components/menu/MenuContent.js +147 -147
- package/dist/components/menu/MenuContent.js.map +1 -1
- package/dist/components/menu/MenuSections.d.ts +1 -1
- package/dist/components/menu/MenuSections.js +1 -1
- package/dist/components/menu/MenuSections.js.map +1 -1
- package/dist/components/menu/PageSelector.d.ts +1 -1
- package/dist/components/menu/PageSelector.js +65 -65
- package/dist/components/menu/PageSelector.js.map +1 -1
- package/dist/components/menu/use-check-text-overflow.js.map +1 -1
- package/dist/layout.css +465 -465
- package/dist/svg/AI.d.ts +1 -1
- package/dist/svg/AI.js +1 -1
- package/dist/svg/EDP.d.ts +1 -1
- package/dist/svg/EDP.js +1 -1
- package/dist/svg/Forbidden.d.ts +1 -1
- package/dist/svg/Forbidden.js +1 -1
- package/dist/svg/HUB.d.ts +1 -1
- package/dist/svg/HUB.js +1 -1
- package/dist/svg/Logo.d.ts +1 -1
- package/dist/svg/Logo.js +1 -1
- package/dist/svg/NotFound.d.ts +1 -1
- package/dist/svg/NotFound.js +1 -1
- package/dist/svg/ServerError.d.ts +1 -1
- package/dist/svg/ServerError.js +1 -1
- package/dist/svg/Unauthenticated.d.ts +1 -1
- package/dist/svg/Unauthenticated.js +1 -1
- package/dist/toaster.js +2 -2
- package/dist/toaster.js.map +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/Layout.tsx +103 -103
- package/src/LayoutOverlayManager.tsx +273 -273
- package/src/components/Dialog.tsx +93 -93
- package/src/components/Header.tsx +29 -29
- package/src/components/OverlayContent.tsx +58 -58
- package/src/components/PortalSwitcher.tsx +147 -147
- package/src/components/SelectionList.tsx +268 -268
- package/src/components/Toaster.tsx +16 -16
- package/src/components/UserMenu.tsx +111 -111
- package/src/components/error/ErrorBoundary.tsx +38 -38
- package/src/components/error/ErrorFeedback.tsx +114 -114
- package/src/components/error/ErrorManager.ts +31 -31
- package/src/components/error/SilentErrorBoundary.tsx +54 -54
- package/src/components/menu/MenuContent.tsx +293 -293
- package/src/components/menu/MenuSections.tsx +268 -268
- package/src/components/menu/PageSelector.tsx +152 -152
- package/src/components/menu/constants.ts +2 -2
- package/src/components/menu/types.ts +112 -112
- package/src/components/menu/use-check-text-overflow.tsx +26 -26
- package/src/components/menu/use-keyboard-controls.tsx +70 -70
- package/src/components/types.ts +15 -15
- package/src/dictionary.ts +25 -25
- package/src/elements.ts +24 -24
- package/src/errors.ts +11 -11
- package/src/index.ts +17 -17
- package/src/layout.css +465 -465
- package/src/svg/AI.tsx +37 -37
- package/src/svg/EDP.tsx +35 -35
- package/src/svg/Forbidden.tsx +22 -22
- package/src/svg/HUB.tsx +35 -35
- package/src/svg/Logo.tsx +35 -35
- package/src/svg/NotFound.tsx +16 -16
- package/src/svg/ServerError.tsx +33 -33
- package/src/svg/Unauthenticated.tsx +16 -16
- package/src/toaster.tsx +76 -76
- package/src/utils.ts +114 -114
- package/tsconfig.json +8 -8
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
import { Flex, IconBox, LinkBox, Text } from '@citric/core'
|
|
2
|
-
import { ChevronDown } from '@citric/icons'
|
|
3
|
-
import { Avatar } from '@citric/ui'
|
|
4
|
-
import { theme } from '@stack-spot/portal-theme'
|
|
5
|
-
import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
|
|
6
|
-
import { useState } from 'react'
|
|
7
|
-
import { styled } from 'styled-components'
|
|
8
|
-
import { SelectionList, SelectionListProps } from './SelectionList'
|
|
9
|
-
|
|
10
|
-
interface Props {
|
|
11
|
-
userName: string,
|
|
12
|
-
email?: string,
|
|
13
|
-
options?: SelectionListProps['items'],
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const USER_MENU_ID = 'userMenu'
|
|
17
|
-
|
|
18
|
-
const UserMenuBox = styled.div`
|
|
19
|
-
.user-menu-header {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
justify-content: center;
|
|
23
|
-
align-items: center;
|
|
24
|
-
padding: 12px;
|
|
25
|
-
border-bottom: 2px solid ${theme.color.light['600']};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.selection-list {
|
|
29
|
-
position: absolute;
|
|
30
|
-
top: var(--header-height);
|
|
31
|
-
right: 20px;
|
|
32
|
-
width: 266px;
|
|
33
|
-
|
|
34
|
-
.selection-list-content {
|
|
35
|
-
border: none;
|
|
36
|
-
padding: 16px 16px 8px;
|
|
37
|
-
background-color: ${theme.color.light['400']};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
li {
|
|
41
|
-
margin: 8px 0;
|
|
42
|
-
& > a {
|
|
43
|
-
border-radius: 6px;
|
|
44
|
-
&:hover, &:focus {
|
|
45
|
-
background: ${theme.color.light['500']};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.username {
|
|
52
|
-
margin: 5px 0 2px 0;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.chevron {
|
|
56
|
-
transition: transform ease-out 0.3s;
|
|
57
|
-
}
|
|
58
|
-
`
|
|
59
|
-
|
|
60
|
-
const UserMenuHeader = ({ userName, email }: Omit<Props, 'options'>) => (
|
|
61
|
-
<div className="user-menu-header">
|
|
62
|
-
<Avatar size="xs">{userName}</Avatar>
|
|
63
|
-
<Text appearance="body1" className="username">{userName}</Text>
|
|
64
|
-
{email && <Text appearance="microtext1" className="email" colorScheme="light.700">{email}</Text>}
|
|
65
|
-
</div>
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
export const UserMenu = ({ userName, email, options }: Props) => {
|
|
69
|
-
const t = useTranslate(dictionary)
|
|
70
|
-
const [visible, setVisible] = useState(false)
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<UserMenuBox>
|
|
74
|
-
<LinkBox
|
|
75
|
-
as="button"
|
|
76
|
-
onClick={() => setVisible(v => !v)}
|
|
77
|
-
aria-controls={USER_MENU_ID}
|
|
78
|
-
aria-expanded={visible}
|
|
79
|
-
aria-label={interpolate(t.accountMenu, [userName])}
|
|
80
|
-
>
|
|
81
|
-
<Flex alignItems="center">
|
|
82
|
-
<Avatar size="xs" aria-label={interpolate(t.accountMenu, [userName])}>{userName}</Avatar>
|
|
83
|
-
<IconBox
|
|
84
|
-
<ChevronDown />
|
|
85
|
-
</IconBox>
|
|
86
|
-
</Flex>
|
|
87
|
-
</LinkBox>
|
|
88
|
-
|
|
89
|
-
{options?.length
|
|
90
|
-
? <SelectionList
|
|
91
|
-
id={USER_MENU_ID}
|
|
92
|
-
visible={visible}
|
|
93
|
-
before={<UserMenuHeader userName={userName} email={email} />}
|
|
94
|
-
items={options!}
|
|
95
|
-
onHide={() => setVisible(false)}
|
|
96
|
-
maxHeight="600px"
|
|
97
|
-
/>
|
|
98
|
-
: null
|
|
99
|
-
}
|
|
100
|
-
</UserMenuBox>
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const dictionary = {
|
|
105
|
-
en: {
|
|
106
|
-
accountMenu: 'Profile menu of $0',
|
|
107
|
-
},
|
|
108
|
-
pt: {
|
|
109
|
-
accountMenu: 'Menu do perfil $0',
|
|
110
|
-
},
|
|
111
|
-
} satisfies Dictionary
|
|
1
|
+
import { Flex, IconBox, LinkBox, Text } from '@citric/core'
|
|
2
|
+
import { ChevronDown } from '@citric/icons'
|
|
3
|
+
import { Avatar } from '@citric/ui'
|
|
4
|
+
import { theme } from '@stack-spot/portal-theme'
|
|
5
|
+
import { Dictionary, interpolate, useTranslate } from '@stack-spot/portal-translate'
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
import { styled } from 'styled-components'
|
|
8
|
+
import { SelectionList, SelectionListProps } from './SelectionList'
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
userName: string,
|
|
12
|
+
email?: string,
|
|
13
|
+
options?: SelectionListProps['items'],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const USER_MENU_ID = 'userMenu'
|
|
17
|
+
|
|
18
|
+
const UserMenuBox = styled.div`
|
|
19
|
+
.user-menu-header {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
align-items: center;
|
|
24
|
+
padding: 12px;
|
|
25
|
+
border-bottom: 2px solid ${theme.color.light['600']};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.selection-list {
|
|
29
|
+
position: absolute;
|
|
30
|
+
top: var(--header-height);
|
|
31
|
+
right: 20px;
|
|
32
|
+
width: 266px;
|
|
33
|
+
|
|
34
|
+
.selection-list-content {
|
|
35
|
+
border: none;
|
|
36
|
+
padding: 16px 16px 8px;
|
|
37
|
+
background-color: ${theme.color.light['400']};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
li {
|
|
41
|
+
margin: 8px 0;
|
|
42
|
+
& > a {
|
|
43
|
+
border-radius: 6px;
|
|
44
|
+
&:hover, &:focus {
|
|
45
|
+
background: ${theme.color.light['500']};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.username {
|
|
52
|
+
margin: 5px 0 2px 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.chevron {
|
|
56
|
+
transition: transform ease-out 0.3s;
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
const UserMenuHeader = ({ userName, email }: Omit<Props, 'options'>) => (
|
|
61
|
+
<div className="user-menu-header">
|
|
62
|
+
<Avatar size="xs">{userName}</Avatar>
|
|
63
|
+
<Text appearance="body1" className="username">{userName}</Text>
|
|
64
|
+
{email && <Text appearance="microtext1" className="email" colorScheme="light.700">{email}</Text>}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
export const UserMenu = ({ userName, email, options }: Props) => {
|
|
69
|
+
const t = useTranslate(dictionary)
|
|
70
|
+
const [visible, setVisible] = useState(false)
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<UserMenuBox>
|
|
74
|
+
<LinkBox
|
|
75
|
+
as="button"
|
|
76
|
+
onClick={() => setVisible(v => !v)}
|
|
77
|
+
aria-controls={USER_MENU_ID}
|
|
78
|
+
aria-expanded={visible}
|
|
79
|
+
aria-label={interpolate(t.accountMenu, [userName])}
|
|
80
|
+
>
|
|
81
|
+
<Flex alignItems="center">
|
|
82
|
+
<Avatar size="xs" aria-label={interpolate(t.accountMenu, [userName])}>{userName}</Avatar>
|
|
83
|
+
<IconBox colorIcon="inverse.500" className="chevron" style={visible ? { transform: 'rotate(180deg)' } : undefined}>
|
|
84
|
+
<ChevronDown />
|
|
85
|
+
</IconBox>
|
|
86
|
+
</Flex>
|
|
87
|
+
</LinkBox>
|
|
88
|
+
|
|
89
|
+
{options?.length
|
|
90
|
+
? <SelectionList
|
|
91
|
+
id={USER_MENU_ID}
|
|
92
|
+
visible={visible}
|
|
93
|
+
before={<UserMenuHeader userName={userName} email={email} />}
|
|
94
|
+
items={options!}
|
|
95
|
+
onHide={() => setVisible(false)}
|
|
96
|
+
maxHeight="600px"
|
|
97
|
+
/>
|
|
98
|
+
: null
|
|
99
|
+
}
|
|
100
|
+
</UserMenuBox>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const dictionary = {
|
|
105
|
+
en: {
|
|
106
|
+
accountMenu: 'Profile menu of $0',
|
|
107
|
+
},
|
|
108
|
+
pt: {
|
|
109
|
+
accountMenu: 'Menu do perfil $0',
|
|
110
|
+
},
|
|
111
|
+
} satisfies Dictionary
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { Component } from 'react'
|
|
2
|
-
import { ErrorFeedback } from './ErrorFeedback'
|
|
3
|
-
import { ErrorDescription, ErrorManager } from './ErrorManager'
|
|
4
|
-
|
|
5
|
-
interface State extends ErrorDescription {
|
|
6
|
-
hasError: boolean,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
children: React.ReactNode,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class ErrorBoundary extends Component<Props, State> {
|
|
14
|
-
constructor(props: Props) {
|
|
15
|
-
super(props)
|
|
16
|
-
this.state = { hasError: false }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
static getDerivedStateFromError(error: any) {
|
|
20
|
-
return { hasError: true, ...ErrorManager.describe(error) }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
componentDidCatch(error: any, errorInfo: any) {
|
|
24
|
-
// eslint-disable-next-line no-console
|
|
25
|
-
console.error(error, errorInfo)
|
|
26
|
-
ErrorManager.runErrorHandler(error)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
componentDidUpdate(prevProps: Readonly<Props>) {
|
|
30
|
-
if (this.props.children !== prevProps.children) this.setState({ hasError: false })
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
render() {
|
|
34
|
-
return this.state.hasError
|
|
35
|
-
? <ErrorFeedback code={this.state.code} message={this.state.message} debug={this.state.debug} />
|
|
36
|
-
: this.props.children
|
|
37
|
-
}
|
|
38
|
-
}
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
import { ErrorFeedback } from './ErrorFeedback'
|
|
3
|
+
import { ErrorDescription, ErrorManager } from './ErrorManager'
|
|
4
|
+
|
|
5
|
+
interface State extends ErrorDescription {
|
|
6
|
+
hasError: boolean,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: React.ReactNode,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
14
|
+
constructor(props: Props) {
|
|
15
|
+
super(props)
|
|
16
|
+
this.state = { hasError: false }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static getDerivedStateFromError(error: any) {
|
|
20
|
+
return { hasError: true, ...ErrorManager.describe(error) }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
componentDidCatch(error: any, errorInfo: any) {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.error(error, errorInfo)
|
|
26
|
+
ErrorManager.runErrorHandler(error)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentDidUpdate(prevProps: Readonly<Props>) {
|
|
30
|
+
if (this.props.children !== prevProps.children) this.setState({ hasError: false })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
return this.state.hasError
|
|
35
|
+
? <ErrorFeedback code={this.state.code} message={this.state.message} debug={this.state.debug} />
|
|
36
|
+
: this.props.children
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
import { Box, Button, Container, Flex, LinkBox, Text } from '@citric/core'
|
|
2
|
-
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
-
import { useState } from 'react'
|
|
4
|
-
import { Forbidden } from '../../svg/Forbidden'
|
|
5
|
-
import { Logo } from '../../svg/Logo'
|
|
6
|
-
import { NotFound } from '../../svg/NotFound'
|
|
7
|
-
import { ServerError } from '../../svg/ServerError'
|
|
8
|
-
import { Unauthenticated } from '../../svg/Unauthenticated'
|
|
9
|
-
import { ErrorDescription } from './ErrorManager'
|
|
10
|
-
|
|
11
|
-
const imageStyle: React.CSSProperties = {
|
|
12
|
-
width: '200px',
|
|
13
|
-
height: '200px',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const imageMap: Record<number, React.ReactElement> = {
|
|
17
|
-
401: <Unauthenticated style={imageStyle} />,
|
|
18
|
-
403: <Forbidden style={imageStyle} />,
|
|
19
|
-
404: <NotFound style={imageStyle} />,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const ErrorFeedback = ({ code = 0, message, debug }: ErrorDescription) => {
|
|
23
|
-
const t = useTranslate(dictionary) as Record<string, string>
|
|
24
|
-
const [showDetails, setShowDetails] = useState(false)
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<Box bg="light.400">
|
|
28
|
-
<Container>
|
|
29
|
-
<Flex alignItems="center" sx={{ padding: 12 }}>
|
|
30
|
-
<Box width={5} sx={{ display: ['block', 'none'] }}>
|
|
31
|
-
<Flex justifyContent="flex-end" pr={20}>
|
|
32
|
-
{imageMap[code] ?? <ServerError style={imageStyle} />}
|
|
33
|
-
</Flex>
|
|
34
|
-
</Box>
|
|
35
|
-
<Box width={[7, 12]}>
|
|
36
|
-
<LinkBox href="/">
|
|
37
|
-
<Logo style={{ width: '130px', height: '30px' }} />
|
|
38
|
-
</LinkBox>
|
|
39
|
-
<Box w={[7, 12]}>
|
|
40
|
-
<Text appearance="h4" mt={5} colorScheme="inverse">
|
|
41
|
-
{code ? `${code}. ` : ''}
|
|
42
|
-
<Text appearance="h4" as="span" colorScheme="light.700">
|
|
43
|
-
{t[`${code}.title`]}
|
|
44
|
-
</Text>
|
|
45
|
-
</Text>
|
|
46
|
-
|
|
47
|
-
<Text appearance="body1" mt={5} colorScheme="inverse">
|
|
48
|
-
{t[`${code}.description`]}
|
|
49
|
-
</Text>
|
|
50
|
-
|
|
51
|
-
<Text appearance="body1" colorScheme="light.700" mt={1}>
|
|
52
|
-
{t[`${code}.help`]}
|
|
53
|
-
</Text>
|
|
54
|
-
{debug && message && (
|
|
55
|
-
<Button appearance="outlined" colorScheme="inverse" onClick={() => setShowDetails(v => !v)}>
|
|
56
|
-
{showDetails ? t.hideDetails : t.showDetails}
|
|
57
|
-
</Button>
|
|
58
|
-
)}
|
|
59
|
-
{showDetails && (
|
|
60
|
-
<Box bg="danger" mt={8} p={4} sx={{ borderRadius: '5px' }}>
|
|
61
|
-
<Text appearance="microtext1" colorScheme="danger.contrastText">{message}</Text>
|
|
62
|
-
</Box>
|
|
63
|
-
)}
|
|
64
|
-
</Box>
|
|
65
|
-
</Box>
|
|
66
|
-
</Flex>
|
|
67
|
-
</Container>
|
|
68
|
-
</Box>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const dictionary = {
|
|
73
|
-
en: {
|
|
74
|
-
altLogo: 'Logo Stackspot',
|
|
75
|
-
'0.title': 'Unknown client error',
|
|
76
|
-
'0.description': 'An unknown error happened while trying to load the resource.',
|
|
77
|
-
'0.help': 'Reload the page and, if it still doesn\'t work, report the error the Stackspot team.',
|
|
78
|
-
'401.title': 'Not authorized',
|
|
79
|
-
'401.description': 'There was a failure loading credentials for this page.',
|
|
80
|
-
'401.help': 'Check if the URL is correct or clear your cache and cookies from your browser and try again.',
|
|
81
|
-
'403.title': 'Private access',
|
|
82
|
-
'403.description': 'The page you have tried to visit is private.',
|
|
83
|
-
'403.help': 'Log in with another account or request access from the person who manages your organization.',
|
|
84
|
-
'404.title': 'Resource not found',
|
|
85
|
-
'404.description': 'This resource no longer exists.',
|
|
86
|
-
'404.help': 'Please try again or request a new URL from the person who manages your organization.',
|
|
87
|
-
'500.title': 'Server error',
|
|
88
|
-
'500.description':
|
|
89
|
-
"We have identified a problem with the server, but don't worry. We are already investigating what happened.",
|
|
90
|
-
'500.help': 'Please try again after a few minutes.',
|
|
91
|
-
showDetails: 'Show Details',
|
|
92
|
-
hideDetails: 'Hide Details',
|
|
93
|
-
},
|
|
94
|
-
pt: {
|
|
95
|
-
altLogo: 'Logo Stackspot',
|
|
96
|
-
'0.title': 'Erro desconhecido (cliente)',
|
|
97
|
-
'0.description': 'Um erro desconhecido aconteceu ao carregar o recurso',
|
|
98
|
-
'0.help': 'Recarregue a página e, se ainda não funcionar, reporte o problema para o time da Stackspot.',
|
|
99
|
-
'401.title': 'Não autorizado',
|
|
100
|
-
'401.description': 'Houve uma falha no carregamento de credenciais dessa página.',
|
|
101
|
-
'401.help': 'Verifique se a URL está correta ou limpe o cache e os cookies de seu navegador e tente novamente.',
|
|
102
|
-
'403.title': 'Acesso privado',
|
|
103
|
-
'403.description': '"A página que você tentou visualizar é particular."',
|
|
104
|
-
'403.help': 'Solicite acesso com o administrador da sua organização.',
|
|
105
|
-
'404.title': 'Recurso não encontrado',
|
|
106
|
-
'404.description': 'Este recurso não existe mais.',
|
|
107
|
-
'404.help': 'Tente novamente ou fale com o administrador da sua organização.',
|
|
108
|
-
'500.title': 'Erro ao exibir o recurso',
|
|
109
|
-
'500.description': 'Mas não se preocupe, já estamos investigando o que aconteceu.',
|
|
110
|
-
'500.help': 'Tente novamente após alguns minutos.',
|
|
111
|
-
showDetails: 'Ver Detalhes',
|
|
112
|
-
hideDetails: 'Esconder Detalhes',
|
|
113
|
-
},
|
|
114
|
-
} satisfies Dictionary
|
|
1
|
+
import { Box, Button, Container, Flex, LinkBox, Text } from '@citric/core'
|
|
2
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Forbidden } from '../../svg/Forbidden'
|
|
5
|
+
import { Logo } from '../../svg/Logo'
|
|
6
|
+
import { NotFound } from '../../svg/NotFound'
|
|
7
|
+
import { ServerError } from '../../svg/ServerError'
|
|
8
|
+
import { Unauthenticated } from '../../svg/Unauthenticated'
|
|
9
|
+
import { ErrorDescription } from './ErrorManager'
|
|
10
|
+
|
|
11
|
+
const imageStyle: React.CSSProperties = {
|
|
12
|
+
width: '200px',
|
|
13
|
+
height: '200px',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const imageMap: Record<number, React.ReactElement> = {
|
|
17
|
+
401: <Unauthenticated style={imageStyle} />,
|
|
18
|
+
403: <Forbidden style={imageStyle} />,
|
|
19
|
+
404: <NotFound style={imageStyle} />,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ErrorFeedback = ({ code = 0, message, debug }: ErrorDescription) => {
|
|
23
|
+
const t = useTranslate(dictionary) as Record<string, string>
|
|
24
|
+
const [showDetails, setShowDetails] = useState(false)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Box bg="light.400">
|
|
28
|
+
<Container>
|
|
29
|
+
<Flex alignItems="center" sx={{ padding: 12 }}>
|
|
30
|
+
<Box width={5} sx={{ display: ['block', 'none'] }}>
|
|
31
|
+
<Flex justifyContent="flex-end" pr={20}>
|
|
32
|
+
{imageMap[code] ?? <ServerError style={imageStyle} />}
|
|
33
|
+
</Flex>
|
|
34
|
+
</Box>
|
|
35
|
+
<Box width={[7, 12]}>
|
|
36
|
+
<LinkBox href="/">
|
|
37
|
+
<Logo style={{ width: '130px', height: '30px' }} />
|
|
38
|
+
</LinkBox>
|
|
39
|
+
<Box w={[7, 12]}>
|
|
40
|
+
<Text appearance="h4" mt={5} colorScheme="inverse">
|
|
41
|
+
{code ? `${code}. ` : ''}
|
|
42
|
+
<Text appearance="h4" as="span" colorScheme="light.700">
|
|
43
|
+
{t[`${code}.title`]}
|
|
44
|
+
</Text>
|
|
45
|
+
</Text>
|
|
46
|
+
|
|
47
|
+
<Text appearance="body1" mt={5} colorScheme="inverse">
|
|
48
|
+
{t[`${code}.description`]}
|
|
49
|
+
</Text>
|
|
50
|
+
|
|
51
|
+
<Text appearance="body1" colorScheme="light.700" mt={1}>
|
|
52
|
+
{t[`${code}.help`]}
|
|
53
|
+
</Text>
|
|
54
|
+
{debug && message && (
|
|
55
|
+
<Button appearance="outlined" colorScheme="inverse" onClick={() => setShowDetails(v => !v)}>
|
|
56
|
+
{showDetails ? t.hideDetails : t.showDetails}
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
{showDetails && (
|
|
60
|
+
<Box bg="danger" mt={8} p={4} sx={{ borderRadius: '5px' }}>
|
|
61
|
+
<Text appearance="microtext1" colorScheme="danger.contrastText">{message}</Text>
|
|
62
|
+
</Box>
|
|
63
|
+
)}
|
|
64
|
+
</Box>
|
|
65
|
+
</Box>
|
|
66
|
+
</Flex>
|
|
67
|
+
</Container>
|
|
68
|
+
</Box>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dictionary = {
|
|
73
|
+
en: {
|
|
74
|
+
altLogo: 'Logo Stackspot',
|
|
75
|
+
'0.title': 'Unknown client error',
|
|
76
|
+
'0.description': 'An unknown error happened while trying to load the resource.',
|
|
77
|
+
'0.help': 'Reload the page and, if it still doesn\'t work, report the error the Stackspot team.',
|
|
78
|
+
'401.title': 'Not authorized',
|
|
79
|
+
'401.description': 'There was a failure loading credentials for this page.',
|
|
80
|
+
'401.help': 'Check if the URL is correct or clear your cache and cookies from your browser and try again.',
|
|
81
|
+
'403.title': 'Private access',
|
|
82
|
+
'403.description': 'The page you have tried to visit is private.',
|
|
83
|
+
'403.help': 'Log in with another account or request access from the person who manages your organization.',
|
|
84
|
+
'404.title': 'Resource not found',
|
|
85
|
+
'404.description': 'This resource no longer exists.',
|
|
86
|
+
'404.help': 'Please try again or request a new URL from the person who manages your organization.',
|
|
87
|
+
'500.title': 'Server error',
|
|
88
|
+
'500.description':
|
|
89
|
+
"We have identified a problem with the server, but don't worry. We are already investigating what happened.",
|
|
90
|
+
'500.help': 'Please try again after a few minutes.',
|
|
91
|
+
showDetails: 'Show Details',
|
|
92
|
+
hideDetails: 'Hide Details',
|
|
93
|
+
},
|
|
94
|
+
pt: {
|
|
95
|
+
altLogo: 'Logo Stackspot',
|
|
96
|
+
'0.title': 'Erro desconhecido (cliente)',
|
|
97
|
+
'0.description': 'Um erro desconhecido aconteceu ao carregar o recurso',
|
|
98
|
+
'0.help': 'Recarregue a página e, se ainda não funcionar, reporte o problema para o time da Stackspot.',
|
|
99
|
+
'401.title': 'Não autorizado',
|
|
100
|
+
'401.description': 'Houve uma falha no carregamento de credenciais dessa página.',
|
|
101
|
+
'401.help': 'Verifique se a URL está correta ou limpe o cache e os cookies de seu navegador e tente novamente.',
|
|
102
|
+
'403.title': 'Acesso privado',
|
|
103
|
+
'403.description': '"A página que você tentou visualizar é particular."',
|
|
104
|
+
'403.help': 'Solicite acesso com o administrador da sua organização.',
|
|
105
|
+
'404.title': 'Recurso não encontrado',
|
|
106
|
+
'404.description': 'Este recurso não existe mais.',
|
|
107
|
+
'404.help': 'Tente novamente ou fale com o administrador da sua organização.',
|
|
108
|
+
'500.title': 'Erro ao exibir o recurso',
|
|
109
|
+
'500.description': 'Mas não se preocupe, já estamos investigando o que aconteceu.',
|
|
110
|
+
'500.help': 'Tente novamente após alguns minutos.',
|
|
111
|
+
showDetails: 'Ver Detalhes',
|
|
112
|
+
hideDetails: 'Esconder Detalhes',
|
|
113
|
+
},
|
|
114
|
+
} satisfies Dictionary
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
export interface ErrorDescription {
|
|
2
|
-
code?: number,
|
|
3
|
-
message?: string,
|
|
4
|
-
debug?: boolean,
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export type DescriptionFn = (error: any) => ErrorDescription
|
|
8
|
-
export type ErrorHandler = (error: any) => void
|
|
9
|
-
|
|
10
|
-
export class ErrorManager {
|
|
11
|
-
private static descriptionFunction: DescriptionFn = error => ({
|
|
12
|
-
message: error.message || `${error}`,
|
|
13
|
-
})
|
|
14
|
-
private static errorHandler: ErrorHandler | undefined
|
|
15
|
-
|
|
16
|
-
static setDescriptionFunction(fn: DescriptionFn) {
|
|
17
|
-
this.descriptionFunction = fn
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static setErrorHandler(handler: ErrorHandler) {
|
|
21
|
-
this.errorHandler = handler
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static describe(error: any) {
|
|
25
|
-
return this.descriptionFunction(error)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static runErrorHandler(error: any) {
|
|
29
|
-
return this.errorHandler?.(error)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
1
|
+
export interface ErrorDescription {
|
|
2
|
+
code?: number,
|
|
3
|
+
message?: string,
|
|
4
|
+
debug?: boolean,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type DescriptionFn = (error: any) => ErrorDescription
|
|
8
|
+
export type ErrorHandler = (error: any) => void
|
|
9
|
+
|
|
10
|
+
export class ErrorManager {
|
|
11
|
+
private static descriptionFunction: DescriptionFn = error => ({
|
|
12
|
+
message: error.message || `${error}`,
|
|
13
|
+
})
|
|
14
|
+
private static errorHandler: ErrorHandler | undefined
|
|
15
|
+
|
|
16
|
+
static setDescriptionFunction(fn: DescriptionFn) {
|
|
17
|
+
this.descriptionFunction = fn
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static setErrorHandler(handler: ErrorHandler) {
|
|
21
|
+
this.errorHandler = handler
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static describe(error: any) {
|
|
25
|
+
return this.descriptionFunction(error)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static runErrorHandler(error: any) {
|
|
29
|
+
return this.errorHandler?.(error)
|
|
30
|
+
}
|
|
31
|
+
}
|