@owlmeans/mui-panel 0.1.3
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/LICENSE +21 -0
- package/README.md +96 -0
- package/build/.gitkeep +0 -0
- package/build/auth/context.d.ts +5 -0
- package/build/auth/context.d.ts.map +1 -0
- package/build/auth/context.js +19 -0
- package/build/auth/context.js.map +1 -0
- package/build/auth/exports.d.ts +25 -0
- package/build/auth/exports.d.ts.map +1 -0
- package/build/auth/exports.js +19 -0
- package/build/auth/exports.js.map +1 -0
- package/build/auth/index.d.ts +5 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +4 -0
- package/build/auth/index.js.map +1 -0
- package/build/auth/modules.d.ts +2 -0
- package/build/auth/modules.d.ts.map +1 -0
- package/build/auth/modules.js +4 -0
- package/build/auth/modules.js.map +1 -0
- package/build/auth/plugins/basic-ed25519.d.ts +3 -0
- package/build/auth/plugins/basic-ed25519.d.ts.map +1 -0
- package/build/auth/plugins/basic-ed25519.js +40 -0
- package/build/auth/plugins/basic-ed25519.js.map +1 -0
- package/build/auth/plugins/exports.d.ts +4 -0
- package/build/auth/plugins/exports.d.ts.map +1 -0
- package/build/auth/plugins/exports.js +4 -0
- package/build/auth/plugins/exports.js.map +1 -0
- package/build/auth/plugins/index.d.ts +6 -0
- package/build/auth/plugins/index.d.ts.map +1 -0
- package/build/auth/plugins/index.js +10 -0
- package/build/auth/plugins/index.js.map +1 -0
- package/build/auth/plugins/re-captcha.d.ts +3 -0
- package/build/auth/plugins/re-captcha.d.ts.map +1 -0
- package/build/auth/plugins/re-captcha.js +51 -0
- package/build/auth/plugins/re-captcha.js.map +1 -0
- package/build/auth/plugins/tunnel-consumer.d.ts +3 -0
- package/build/auth/plugins/tunnel-consumer.d.ts.map +1 -0
- package/build/auth/plugins/tunnel-consumer.js +38 -0
- package/build/auth/plugins/tunnel-consumer.js.map +1 -0
- package/build/auth/types.d.ts +10 -0
- package/build/auth/types.d.ts.map +1 -0
- package/build/auth/types.js +2 -0
- package/build/auth/types.js.map +1 -0
- package/build/components/block.d.ts +4 -0
- package/build/components/block.d.ts.map +1 -0
- package/build/components/block.js +15 -0
- package/build/components/block.js.map +1 -0
- package/build/components/button/index.d.ts +3 -0
- package/build/components/button/index.d.ts.map +1 -0
- package/build/components/button/index.js +2 -0
- package/build/components/button/index.js.map +1 -0
- package/build/components/button/selector.d.ts +4 -0
- package/build/components/button/selector.d.ts.map +1 -0
- package/build/components/button/selector.js +8 -0
- package/build/components/button/selector.js.map +1 -0
- package/build/components/button/types.d.ts +7 -0
- package/build/components/button/types.d.ts.map +1 -0
- package/build/components/button/types.js +2 -0
- package/build/components/button/types.js.map +1 -0
- package/build/components/form/button/component.d.ts +5 -0
- package/build/components/form/button/component.d.ts.map +1 -0
- package/build/components/form/button/component.js +34 -0
- package/build/components/form/button/component.js.map +1 -0
- package/build/components/form/button/index.d.ts +3 -0
- package/build/components/form/button/index.d.ts.map +1 -0
- package/build/components/form/button/index.js +3 -0
- package/build/components/form/button/index.js.map +1 -0
- package/build/components/form/button/types.d.ts +15 -0
- package/build/components/form/button/types.d.ts.map +1 -0
- package/build/components/form/button/types.js +2 -0
- package/build/components/form/button/types.js.map +1 -0
- package/build/components/form/component.d.ts +4 -0
- package/build/components/form/component.d.ts.map +1 -0
- package/build/components/form/component.js +59 -0
- package/build/components/form/component.js.map +1 -0
- package/build/components/form/index.d.ts +3 -0
- package/build/components/form/index.d.ts.map +1 -0
- package/build/components/form/index.js +2 -0
- package/build/components/form/index.js.map +1 -0
- package/build/components/form/text/component.d.ts +4 -0
- package/build/components/form/text/component.d.ts.map +1 -0
- package/build/components/form/text/component.js +34 -0
- package/build/components/form/text/component.js.map +1 -0
- package/build/components/form/text/index.d.ts +3 -0
- package/build/components/form/text/index.d.ts.map +1 -0
- package/build/components/form/text/index.js +3 -0
- package/build/components/form/text/index.js.map +1 -0
- package/build/components/form/text/types.d.ts +11 -0
- package/build/components/form/text/types.d.ts.map +1 -0
- package/build/components/form/text/types.js +2 -0
- package/build/components/form/text/types.js.map +1 -0
- package/build/components/form/types.d.ts +6 -0
- package/build/components/form/types.d.ts.map +1 -0
- package/build/components/form/types.js +2 -0
- package/build/components/form/types.js.map +1 -0
- package/build/components/helper.d.ts +6 -0
- package/build/components/helper.d.ts.map +1 -0
- package/build/components/helper.js +70 -0
- package/build/components/helper.js.map +1 -0
- package/build/components/index.d.ts +14 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +13 -0
- package/build/components/index.js.map +1 -0
- package/build/components/layout/component.d.ts +4 -0
- package/build/components/layout/component.d.ts.map +1 -0
- package/build/components/layout/component.js +6 -0
- package/build/components/layout/component.js.map +1 -0
- package/build/components/layout/index.d.ts +3 -0
- package/build/components/layout/index.d.ts.map +1 -0
- package/build/components/layout/index.js +3 -0
- package/build/components/layout/index.js.map +1 -0
- package/build/components/layout/types.d.ts +4 -0
- package/build/components/layout/types.d.ts.map +1 -0
- package/build/components/layout/types.js +2 -0
- package/build/components/layout/types.js.map +1 -0
- package/build/components/link.d.ts +4 -0
- package/build/components/link.d.ts.map +1 -0
- package/build/components/link.js +27 -0
- package/build/components/link.js.map +1 -0
- package/build/components/panel-app/component.d.ts +4 -0
- package/build/components/panel-app/component.d.ts.map +1 -0
- package/build/components/panel-app/component.js +11 -0
- package/build/components/panel-app/component.js.map +1 -0
- package/build/components/panel-app/index.d.ts +3 -0
- package/build/components/panel-app/index.d.ts.map +1 -0
- package/build/components/panel-app/index.js +3 -0
- package/build/components/panel-app/index.js.map +1 -0
- package/build/components/panel-app/types.d.ts +6 -0
- package/build/components/panel-app/types.d.ts.map +1 -0
- package/build/components/panel-app/types.js +2 -0
- package/build/components/panel-app/types.js.map +1 -0
- package/build/components/status.d.ts +4 -0
- package/build/components/status.d.ts.map +1 -0
- package/build/components/status.js +19 -0
- package/build/components/status.js.map +1 -0
- package/build/components/text.d.ts +4 -0
- package/build/components/text.d.ts.map +1 -0
- package/build/components/text.js +9 -0
- package/build/components/text.js.map +1 -0
- package/build/components/types.d.ts +32 -0
- package/build/components/types.d.ts.map +1 -0
- package/build/components/types.js +2 -0
- package/build/components/types.js.map +1 -0
- package/build/components/uploader/image.d.ts +4 -0
- package/build/components/uploader/image.d.ts.map +1 -0
- package/build/components/uploader/image.js +24 -0
- package/build/components/uploader/image.js.map +1 -0
- package/build/components/uploader/index.d.ts +2 -0
- package/build/components/uploader/index.d.ts.map +1 -0
- package/build/components/uploader/index.js +2 -0
- package/build/components/uploader/index.js.map +1 -0
- package/build/components/uploader/types.d.ts +5 -0
- package/build/components/uploader/types.d.ts.map +1 -0
- package/build/components/uploader/types.js +2 -0
- package/build/components/uploader/types.js.map +1 -0
- package/build/context.d.ts +4 -0
- package/build/context.d.ts.map +1 -0
- package/build/context.js +12 -0
- package/build/context.js.map +1 -0
- package/build/exports.d.ts +19 -0
- package/build/exports.d.ts.map +1 -0
- package/build/exports.js +17 -0
- package/build/exports.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/main.d.ts +5 -0
- package/build/main.d.ts.map +1 -0
- package/build/main.js +14 -0
- package/build/main.js.map +1 -0
- package/build/modules.d.ts +2 -0
- package/build/modules.d.ts.map +1 -0
- package/build/modules.js +4 -0
- package/build/modules.js.map +1 -0
- package/build/types.d.ts +9 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +95 -0
- package/src/auth/context.ts +27 -0
- package/src/auth/exports.ts +29 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/modules.ts +5 -0
- package/src/auth/plugins/basic-ed25519.tsx +57 -0
- package/src/auth/plugins/exports.ts +4 -0
- package/src/auth/plugins/index.ts +15 -0
- package/src/auth/plugins/re-captcha.tsx +73 -0
- package/src/auth/plugins/tunnel-consumer.tsx +65 -0
- package/src/auth/types.ts +11 -0
- package/src/components/block.tsx +27 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/button/selector.tsx +16 -0
- package/src/components/button/types.ts +7 -0
- package/src/components/form/button/component.tsx +53 -0
- package/src/components/form/button/index.ts +3 -0
- package/src/components/form/button/types.ts +16 -0
- package/src/components/form/component.tsx +101 -0
- package/src/components/form/index.ts +3 -0
- package/src/components/form/text/component.tsx +45 -0
- package/src/components/form/text/index.ts +3 -0
- package/src/components/form/text/types.ts +11 -0
- package/src/components/form/types.ts +6 -0
- package/src/components/helper.ts +79 -0
- package/src/components/index.ts +17 -0
- package/src/components/layout/component.tsx +7 -0
- package/src/components/layout/index.ts +3 -0
- package/src/components/layout/types.ts +4 -0
- package/src/components/link.tsx +34 -0
- package/src/components/panel-app/component.tsx +20 -0
- package/src/components/panel-app/index.ts +3 -0
- package/src/components/panel-app/types.ts +6 -0
- package/src/components/status.tsx +23 -0
- package/src/components/text.tsx +14 -0
- package/src/components/types.ts +35 -0
- package/src/components/uploader/image.tsx +31 -0
- package/src/components/uploader/index.ts +2 -0
- package/src/components/uploader/types.ts +6 -0
- package/src/context.ts +18 -0
- package/src/exports.ts +22 -0
- package/src/index.ts +9 -0
- package/src/main.tsx +19 -0
- package/src/modules.ts +5 -0
- package/src/types.ts +11 -0
- package/tests/smoke.spec.ts +24 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AuthenticationControl, AuthenticationRenderer } from '@owlmeans/client-auth/manager'
|
|
2
|
+
|
|
3
|
+
import LinearProgress from '@mui/material/LinearProgress'
|
|
4
|
+
import { AuthenticationStage, AuthorizationError, CMOD_RECAPTCHA, DISPATCHER, GUEST_ID } from '@owlmeans/auth'
|
|
5
|
+
import { useValue } from '@owlmeans/client'
|
|
6
|
+
import { PLUGINS } from '@owlmeans/client-context'
|
|
7
|
+
import ReCAPTCHA from 'react-google-recaptcha'
|
|
8
|
+
import { useCallback, useState } from 'react'
|
|
9
|
+
import { PanelContext } from '@owlmeans/client-panel'
|
|
10
|
+
import { Text } from '../../components/text.js'
|
|
11
|
+
import Stack from '@mui/material/Stack'
|
|
12
|
+
import Box from '@mui/material/Box'
|
|
13
|
+
import type { SxProps } from '@mui/material/styles'
|
|
14
|
+
import type { AppContext, Module } from '@owlmeans/web-client'
|
|
15
|
+
import { useContext } from '@owlmeans/web-client'
|
|
16
|
+
import type { AuthRequest } from '@owlmeans/auth-common'
|
|
17
|
+
|
|
18
|
+
export const ReCaptchaAuthUIPlugin: AuthenticationRenderer = ({ stage, control }) => {
|
|
19
|
+
const context = useContext()
|
|
20
|
+
const [loading, setLoading] = useState(true)
|
|
21
|
+
const config = useValue(() => context.getConfigResource(PLUGINS).get(CMOD_RECAPTCHA), [])
|
|
22
|
+
const finish = useCallback(createFinish(context, control), [])
|
|
23
|
+
|
|
24
|
+
const style: SxProps = {
|
|
25
|
+
maxWidth: { xs: '100%', sm: '100%', md: '50%' }
|
|
26
|
+
}
|
|
27
|
+
const loadingStyle: SxProps = {
|
|
28
|
+
width: { xs: '100%', sm: '100%', md: '50%' }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const content = () => {
|
|
32
|
+
switch (config?.value != null ? stage : null) {
|
|
33
|
+
case AuthenticationStage.Authenticate:
|
|
34
|
+
return <Stack direction="column" sx={style} justifyContent="center" alignItems="center">
|
|
35
|
+
<Text name="guideline" center />
|
|
36
|
+
<Box sx={{ pt: 2 }}>
|
|
37
|
+
<ReCAPTCHA sitekey={config?.value as string ?? ''} onChange={finish}
|
|
38
|
+
asyncScriptOnLoad={() => setLoading(false)} />
|
|
39
|
+
</Box>
|
|
40
|
+
</Stack>
|
|
41
|
+
default:
|
|
42
|
+
return <LinearProgress sx={loadingStyle} />
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return <PanelContext ns="lib" prefix="re-captcha" resource="client-panel-auth">
|
|
47
|
+
<Stack direction="column" justifyContent="center" alignItems="center">
|
|
48
|
+
{content()}
|
|
49
|
+
{loading && <LinearProgress sx={loadingStyle} />}
|
|
50
|
+
</Stack>
|
|
51
|
+
</PanelContext>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const createFinish = (context: AppContext, control: AuthenticationControl) => async (token: string | null) => {
|
|
55
|
+
if (token == null) {
|
|
56
|
+
throw new AuthorizationError('re-captcha-token')
|
|
57
|
+
}
|
|
58
|
+
const authToken = await control.authenticate({ credential: token, userId: GUEST_ID })
|
|
59
|
+
|
|
60
|
+
if (authToken.token === '') {
|
|
61
|
+
console.timeLog('empty token')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
const [url] = await context.module<Module<string, AuthRequest>>(DISPATCHER)
|
|
65
|
+
.call({ query: authToken })
|
|
66
|
+
|
|
67
|
+
control.setStage?.(control.stage = AuthenticationStage.Authenticated)
|
|
68
|
+
|
|
69
|
+
// Give some time - that is really not cenessary - actually we need
|
|
70
|
+
// to do it on the layout finished its stuff.
|
|
71
|
+
// @TODO fix it for react native (we need some other solution for redirects context indepedent)
|
|
72
|
+
setTimeout(() => window.location.href = url, 100)
|
|
73
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import LinearProgress from '@mui/material/LinearProgress'
|
|
2
|
+
import { AuthenticationStage } from '@owlmeans/auth'
|
|
3
|
+
import type { RelyToken } from '@owlmeans/auth'
|
|
4
|
+
import type { TunnelAuthenticationRenderer } from '@owlmeans/client-auth/manager/plugins'
|
|
5
|
+
import { PinSchema } from '@owlmeans/client-auth/manager/plugins'
|
|
6
|
+
import { Form } from '../../components/form/component.js'
|
|
7
|
+
import { TextInput } from '../../components/form/text/component.js'
|
|
8
|
+
// import { Text } from '../../components/text.js'
|
|
9
|
+
import { useMemo } from 'react'
|
|
10
|
+
import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
|
|
11
|
+
import { Block } from '../../components/block.js'
|
|
12
|
+
import { BlockScaling } from '@owlmeans/client-panel'
|
|
13
|
+
import { Status } from '../../components/status.js'
|
|
14
|
+
import type { SxProps } from '@mui/material/styles'
|
|
15
|
+
import { useTheme } from '@mui/material/styles'
|
|
16
|
+
import { Button } from '../../components/form/button/component.js'
|
|
17
|
+
import { QRCodeCanvas } from 'qrcode.react'
|
|
18
|
+
import Box from '@mui/material/Box'
|
|
19
|
+
|
|
20
|
+
export const TunnelConsumerUIPlugin: TunnelAuthenticationRenderer = ({ type, stage, control, params, submit }) => {
|
|
21
|
+
const rely = useMemo(() => {
|
|
22
|
+
if (control.allowance == null) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return makeEnvelopeModel<RelyToken>(
|
|
26
|
+
makeEnvelopeModel<string>(control.allowance.challenge, EnvelopeKind.Wrap).message(true)
|
|
27
|
+
, EnvelopeKind.Wrap
|
|
28
|
+
).message()
|
|
29
|
+
}, [stage])
|
|
30
|
+
|
|
31
|
+
const theme = useTheme()
|
|
32
|
+
const prefix = "prefix" in params ? params.prefix as string : ""
|
|
33
|
+
const i18n = { ns: "lib", resource: 'client-panel-auth' }
|
|
34
|
+
|
|
35
|
+
const loadingStyle: SxProps = {
|
|
36
|
+
width: { xs: '100%', sm: '100%', md: '50%' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (stage) {
|
|
40
|
+
case AuthenticationStage.Authenticate:
|
|
41
|
+
return <Form decorate name={type} validation={PinSchema} onSubmit={submit} i18n={i18n}>
|
|
42
|
+
<Box width="fit-content" margin="auto">
|
|
43
|
+
{rely?.token != null && <QRCodeCanvas size={256}
|
|
44
|
+
value={`${prefix}${rely?.token ?? ""}`}
|
|
45
|
+
fgColor={theme.palette.primary.dark}
|
|
46
|
+
bgColor={theme.palette.background.paper}
|
|
47
|
+
/>}
|
|
48
|
+
</Box>
|
|
49
|
+
{/* <Text>{rely?.token}</Text> */}
|
|
50
|
+
<TextInput name="pin" label hint />
|
|
51
|
+
</Form>
|
|
52
|
+
case AuthenticationStage.Error:
|
|
53
|
+
return <Block horizontal={BlockScaling.Half} i18n={i18n} Actions={() =>
|
|
54
|
+
// @TODO document reload looks a bit dirty - cause we can loose the flow
|
|
55
|
+
<Button label="reset" onClick={async () => { document.location.reload() }} />
|
|
56
|
+
// <Button label="reset" onClick={async () => {
|
|
57
|
+
// await control.updateStage(AuthenticationStage.Authenticate)
|
|
58
|
+
// }} />
|
|
59
|
+
}>
|
|
60
|
+
<Status error={control.error} ok={false} />
|
|
61
|
+
</Block>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return <LinearProgress sx={loadingStyle} />
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
2
|
+
import type { ClientContext } from '@owlmeans/client'
|
|
3
|
+
import type { WithFlowConfig } from '@owlmeans/flow'
|
|
4
|
+
import type { FlowService } from '@owlmeans/web-flow'
|
|
5
|
+
|
|
6
|
+
export interface AppConfig extends ClientConfig, WithFlowConfig {
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AppContext<C extends AppConfig = AppConfig> extends ClientContext<C> {
|
|
10
|
+
flow: () => FlowService
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { BlockProps } from './types.js'
|
|
4
|
+
import Card from '@mui/material/Card'
|
|
5
|
+
import CardContent from '@mui/material/CardContent'
|
|
6
|
+
import { PanelContext, usePanelHelper } from '@owlmeans/client-panel'
|
|
7
|
+
import type { SxProps } from '@mui/material/styles'
|
|
8
|
+
import { useTheme } from '@mui/material/styles'
|
|
9
|
+
import CardActions from '@mui/material/CardActions'
|
|
10
|
+
import { scalingToStyles } from './helper.js'
|
|
11
|
+
|
|
12
|
+
export const Block: FC<BlockProps> = ({ children, horizontal, vertical, Actions, i18n, styles }) => {
|
|
13
|
+
const theme = useTheme()
|
|
14
|
+
|
|
15
|
+
const style: SxProps = useMemo(() => scalingToStyles(horizontal, vertical, theme), [horizontal])
|
|
16
|
+
|
|
17
|
+
const panelProps = { ...usePanelHelper(), ...i18n }
|
|
18
|
+
|
|
19
|
+
return <PanelContext {...panelProps}>
|
|
20
|
+
<Card sx={{ ...style, ...styles } as SxProps}>
|
|
21
|
+
<CardContent>{children}</CardContent>
|
|
22
|
+
{Actions != null && <CardActions sx={{ flexDirection: "row", justifyContent: "flex-end", pr: 2, pb: 2 }}>
|
|
23
|
+
<Actions />
|
|
24
|
+
</CardActions>}
|
|
25
|
+
</Card>
|
|
26
|
+
</PanelContext>
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import ButtonGroup from '@mui/material/ButtonGroup'
|
|
3
|
+
import type { FC } from 'react'
|
|
4
|
+
import type { SelectorProps } from './types.js'
|
|
5
|
+
import { Button } from '../form/button/component.js'
|
|
6
|
+
|
|
7
|
+
export const ButtonSelector: FC<SelectorProps> = ({ name, options, current, onSelect }) => {
|
|
8
|
+
const prefix = name != null ? `${name}.` : ''
|
|
9
|
+
return <ButtonGroup>
|
|
10
|
+
{options.map(
|
|
11
|
+
option => <Button key={option} label={`${prefix}${option}`}
|
|
12
|
+
onClick={() => onSelect?.(option)}
|
|
13
|
+
variant={current === option ? 'contained' : 'outlined'} />
|
|
14
|
+
)}
|
|
15
|
+
</ButtonGroup>
|
|
16
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import { memo } from 'react'
|
|
4
|
+
import type { ButtonProps, SubmitProps } from './types'
|
|
5
|
+
|
|
6
|
+
import MUIButton from '@mui/material/Button'
|
|
7
|
+
import CircularProgress from '@mui/material/CircularProgress'
|
|
8
|
+
|
|
9
|
+
import { useFormContext } from 'react-hook-form'
|
|
10
|
+
import { I18nProps, useCommonI18n, useI18nApp, useI18nLib } from '@owlmeans/client-i18n'
|
|
11
|
+
import { useContext } from '@owlmeans/client'
|
|
12
|
+
import { useFormI18n, usePanelHelper } from '@owlmeans/client-panel'
|
|
13
|
+
|
|
14
|
+
export const Button: FC<ButtonProps> = memo(({ label, onClick, i18n, loader, size, fullWidth, variant = 'contained' }) => {
|
|
15
|
+
const context = useContext()
|
|
16
|
+
const panel = usePanelHelper()
|
|
17
|
+
const t = useCommonI18n(
|
|
18
|
+
i18n?.resource ?? panel.resource ?? context.cfg.service,
|
|
19
|
+
i18n?.ns ?? panel.ns,
|
|
20
|
+
i18n?.prefix ?? panel.prefix
|
|
21
|
+
)
|
|
22
|
+
const appT = useI18nApp(context.cfg.service, 'buttons')
|
|
23
|
+
const libT = useI18nLib('client-panel', 'buttons')
|
|
24
|
+
label = useMemo(() => i18n?.suppress ? label : t(label, {
|
|
25
|
+
defaultValue: appT(label, { defaultValue: libT(label) })
|
|
26
|
+
}), [i18n?.suppress, label])
|
|
27
|
+
|
|
28
|
+
size = size ?? 'medium'
|
|
29
|
+
const progressSize = size === 'large'
|
|
30
|
+
? 20
|
|
31
|
+
: size === 'medium' ? 16 : 14
|
|
32
|
+
|
|
33
|
+
return <MUIButton variant={variant as any} size={size} fullWidth={fullWidth}
|
|
34
|
+
startIcon={loader != null && loader.opened === true ? <CircularProgress size={progressSize} /> : undefined}
|
|
35
|
+
disabled={loader != null && loader.opened === true}
|
|
36
|
+
onClick={onClick}>{label}</MUIButton>
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const SubmitButton: FC<SubmitProps> = memo((props) => {
|
|
40
|
+
let { i18n, label } = props
|
|
41
|
+
const { handleSubmit } = useFormContext()
|
|
42
|
+
const t = useFormI18n()
|
|
43
|
+
|
|
44
|
+
label = label ?? 'submit'
|
|
45
|
+
const _i18n: I18nProps["i18n"] = { ...i18n }
|
|
46
|
+
_i18n.suppress = true
|
|
47
|
+
|
|
48
|
+
return <Button {...props} label={t(label)} i18n={_i18n}
|
|
49
|
+
onClick={handleSubmit(
|
|
50
|
+
props.onSubmit ?? props.onClick ?? (() => { console.info('Empty submit') }),
|
|
51
|
+
problem => console.error('Failed to submit form with error: ', problem)
|
|
52
|
+
)} />
|
|
53
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Toggleable } from '@owlmeans/client'
|
|
2
|
+
import type { I18nProps } from '@owlmeans/client-i18n'
|
|
3
|
+
|
|
4
|
+
export interface ButtonProps extends I18nProps {
|
|
5
|
+
size?: 'small' | 'medium' | 'large'
|
|
6
|
+
fullWidth?: boolean
|
|
7
|
+
variant?: string
|
|
8
|
+
label: string
|
|
9
|
+
loader?: Toggleable
|
|
10
|
+
onClick?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SubmitProps extends Omit<ButtonProps, "label"> {
|
|
14
|
+
label?: string
|
|
15
|
+
onSubmit?: (data: any) => Promise<void> | void
|
|
16
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useCallback, useMemo } from 'react'
|
|
3
|
+
import { FormProvider, useForm } from 'react-hook-form'
|
|
4
|
+
import { ajvResolver } from '@hookform/resolvers/ajv'
|
|
5
|
+
import Grid from '@mui/material/Grid'
|
|
6
|
+
import Card from '@mui/material/Card'
|
|
7
|
+
import CardContent from '@mui/material/CardContent'
|
|
8
|
+
import CardActions from '@mui/material/CardActions'
|
|
9
|
+
import { FormContext, schemaToFormDefault } from '@owlmeans/client-panel'
|
|
10
|
+
import type { JSONSchemaType } from 'ajv'
|
|
11
|
+
import Ajv from 'ajv'
|
|
12
|
+
import formatsPlugin from 'ajv-formats'
|
|
13
|
+
import type { SxProps } from '@mui/material/styles'
|
|
14
|
+
import { useTheme } from '@mui/material/styles'
|
|
15
|
+
import { SubmitButton } from './button/component.js'
|
|
16
|
+
import { useToggle } from '@owlmeans/client'
|
|
17
|
+
import { scalingToStyles } from '../helper.js'
|
|
18
|
+
import { ResilientError } from '@owlmeans/error'
|
|
19
|
+
import { Status } from '../status.js'
|
|
20
|
+
import type { WebFormProps } from './types.js'
|
|
21
|
+
|
|
22
|
+
const ajv = new Ajv({ coerceTypes: true })
|
|
23
|
+
formatsPlugin(ajv)
|
|
24
|
+
|
|
25
|
+
export const Form: FC<WebFormProps> = (props) => {
|
|
26
|
+
const {
|
|
27
|
+
defaults, children, formRef, validation, name, horizontal, vertical,
|
|
28
|
+
decorate, onSubmit, i18n, styles
|
|
29
|
+
} = props
|
|
30
|
+
const theme = useTheme()
|
|
31
|
+
const _defaults = useMemo(
|
|
32
|
+
() => defaults ?? (validation != null ? schemaToFormDefault(validation) : undefined),
|
|
33
|
+
[name, defaults != null, validation != null]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const loader = useToggle(false)
|
|
37
|
+
|
|
38
|
+
const form = useForm({
|
|
39
|
+
mode: 'all',
|
|
40
|
+
defaultValues: _defaults,
|
|
41
|
+
resolver: validation
|
|
42
|
+
? ajvResolver(validation as JSONSchemaType<unknown>, { formats: ajv.formats, coerceTypes: true })
|
|
43
|
+
: undefined,
|
|
44
|
+
delayError: 300
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const update = useCallback(((data: Record<string, any>) => {
|
|
48
|
+
const fields = validation != null
|
|
49
|
+
? Object.keys((validation as JSONSchemaType<any>).properties)
|
|
50
|
+
: Object.keys(data)
|
|
51
|
+
fields.forEach(key => {
|
|
52
|
+
form.setValue(key, data[key])
|
|
53
|
+
})
|
|
54
|
+
}) as <T>(data: T) => void, [name])
|
|
55
|
+
|
|
56
|
+
const setError = useCallback((error: unknown, target: string = 'root') => {
|
|
57
|
+
form.setError(target, {
|
|
58
|
+
message: ResilientError.ensure(
|
|
59
|
+
error instanceof Error ? error : `${error}`
|
|
60
|
+
).marshal().message
|
|
61
|
+
})
|
|
62
|
+
}, [name])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if (formRef != null) {
|
|
66
|
+
formRef.current = { form, update, loader, error: setError }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const style: SxProps = useMemo(() => scalingToStyles(horizontal, vertical, theme), [horizontal])
|
|
70
|
+
|
|
71
|
+
const content = () =>
|
|
72
|
+
<Grid container direction="column" justifyContent="flex-start" alignItems="stretch"
|
|
73
|
+
rowSpacing={2} sx={!decorate ? style : {}}>{
|
|
74
|
+
Array.isArray(children)
|
|
75
|
+
? children.map((child, index) =>
|
|
76
|
+
<Grid key={index}>{child}</Grid>
|
|
77
|
+
) : children
|
|
78
|
+
}</Grid>
|
|
79
|
+
|
|
80
|
+
if (decorate === true) {
|
|
81
|
+
const root = form.getFieldState('root')
|
|
82
|
+
return <FormProvider {...form}>
|
|
83
|
+
<FormContext {...props} loader={loader}>
|
|
84
|
+
<Card sx={{ ...style, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', ...styles } as SxProps}>
|
|
85
|
+
<CardContent>
|
|
86
|
+
{content()}
|
|
87
|
+
{root.invalid && root.error?.message &&
|
|
88
|
+
<Status ok={false} i18n={i18n} error={ResilientError.ensure(root.error.message)} />}
|
|
89
|
+
</CardContent>
|
|
90
|
+
{onSubmit != null ? <CardActions sx={{ flexDirection: 'row', justifyContent: 'flex-end', pr: 2, pb: 2 }}>
|
|
91
|
+
<SubmitButton loader={loader} onSubmit={async data => onSubmit(data, update)} />
|
|
92
|
+
</CardActions> : undefined}
|
|
93
|
+
</Card>
|
|
94
|
+
</FormContext>
|
|
95
|
+
</FormProvider>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return <FormProvider {...form}>
|
|
99
|
+
<FormContext {...props} loader={loader}>{content()}</FormContext>
|
|
100
|
+
</FormProvider>
|
|
101
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useFormContext, Controller } from 'react-hook-form'
|
|
3
|
+
import { TextInputProps } from './types.js'
|
|
4
|
+
|
|
5
|
+
import TextField from '@mui/material/TextField'
|
|
6
|
+
import { useFormError, useFormI18n } from '@owlmeans/client-panel'
|
|
7
|
+
import { useClientFormContext } from '@owlmeans/client-panel'
|
|
8
|
+
|
|
9
|
+
export const TextInput: FC<TextInputProps> = ({ name, label, placeholder, hint, type, def, disableAutocomplete }) => {
|
|
10
|
+
const { control } = useFormContext()
|
|
11
|
+
const t = useFormI18n()
|
|
12
|
+
const key = name
|
|
13
|
+
if (typeof label === 'boolean' && label) {
|
|
14
|
+
label = t(`${key}.label`)
|
|
15
|
+
} else {
|
|
16
|
+
label = undefined
|
|
17
|
+
}
|
|
18
|
+
if (typeof placeholder === 'boolean' && placeholder) {
|
|
19
|
+
placeholder = t(`${key}.placeholder`)
|
|
20
|
+
} else {
|
|
21
|
+
placeholder = undefined
|
|
22
|
+
}
|
|
23
|
+
if (typeof hint === 'boolean' && hint) {
|
|
24
|
+
hint = t(`${key}.hint`)
|
|
25
|
+
} else {
|
|
26
|
+
hint = undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <Controller control={control} name={name} defaultValue={def} render={
|
|
30
|
+
({ field, fieldState }) => {
|
|
31
|
+
const error = useFormError(name, fieldState.error)
|
|
32
|
+
const { loader } = useClientFormContext()
|
|
33
|
+
|
|
34
|
+
return <TextField fullWidth {...field}
|
|
35
|
+
error={fieldState.error != null}
|
|
36
|
+
disabled={loader != null && loader.opened === true}
|
|
37
|
+
label={label}
|
|
38
|
+
type={type ?? 'text'}
|
|
39
|
+
placeholder={placeholder}
|
|
40
|
+
autoComplete={disableAutocomplete ? 'off' : 'on'}
|
|
41
|
+
helperText={error ?? hint}
|
|
42
|
+
/>
|
|
43
|
+
}
|
|
44
|
+
} />
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FormFieldProps } from '@owlmeans/client-panel'
|
|
2
|
+
import type { HTMLInputTypeAttribute } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface TextInputProps extends FormFieldProps {
|
|
5
|
+
name: string
|
|
6
|
+
label?: string | boolean
|
|
7
|
+
placeholder?: string | boolean
|
|
8
|
+
hint?: string | boolean
|
|
9
|
+
type?: HTMLInputTypeAttribute
|
|
10
|
+
disableAutocomplete?: boolean
|
|
11
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { SxProps, Theme } from '@mui/material/styles'
|
|
2
|
+
import { useTheme } from '@mui/material/styles'
|
|
3
|
+
import useMediaQuery from '@mui/material/useMediaQuery'
|
|
4
|
+
import { BlockScaling } from '@owlmeans/client-panel'
|
|
5
|
+
|
|
6
|
+
export const scalingToStyles = (
|
|
7
|
+
horizontal?: BlockScaling,
|
|
8
|
+
vertical?: BlockScaling,
|
|
9
|
+
theme?: Theme
|
|
10
|
+
): SxProps => {
|
|
11
|
+
const style: SxProps = {}
|
|
12
|
+
switch (horizontal) {
|
|
13
|
+
case BlockScaling.Half:
|
|
14
|
+
Object.assign(style, {
|
|
15
|
+
maxWidth: '50%',
|
|
16
|
+
[theme?.breakpoints.down('md') ?? 'xs']: {
|
|
17
|
+
maxWidth: '90%'
|
|
18
|
+
},
|
|
19
|
+
flexGrow: 1
|
|
20
|
+
})
|
|
21
|
+
break
|
|
22
|
+
case BlockScaling.Wide:
|
|
23
|
+
Object.assign(style, { mx: '10%', flexGrow: 1 })
|
|
24
|
+
break
|
|
25
|
+
case BlockScaling.Full:
|
|
26
|
+
Object.assign(style, { flexGrow: 1 })
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (vertical) {
|
|
31
|
+
case BlockScaling.Half:
|
|
32
|
+
Object.assign(style, {
|
|
33
|
+
maxHeight: '50%',
|
|
34
|
+
[theme?.breakpoints.down('md') ?? 'xs']: {
|
|
35
|
+
maxHeight: '90%'
|
|
36
|
+
},
|
|
37
|
+
flexGrow: 1
|
|
38
|
+
})
|
|
39
|
+
break
|
|
40
|
+
case BlockScaling.Wide:
|
|
41
|
+
Object.assign(style, { my: '10%', flexGrow: 1 })
|
|
42
|
+
break
|
|
43
|
+
case BlockScaling.Full:
|
|
44
|
+
Object.assign(style, { height: '100%', flexGrow: 1 })
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return style
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const useBreakPoint = () => {
|
|
52
|
+
const theme = useTheme()
|
|
53
|
+
|
|
54
|
+
// Define breakpoints
|
|
55
|
+
const isXs = useMediaQuery(theme.breakpoints.only('xs'))
|
|
56
|
+
const isSm = useMediaQuery(theme.breakpoints.only('sm'))
|
|
57
|
+
const isMd = useMediaQuery(theme.breakpoints.only('md'))
|
|
58
|
+
const isLg = useMediaQuery(theme.breakpoints.only('lg'))
|
|
59
|
+
const isXl = useMediaQuery(theme.breakpoints.only('xl'))
|
|
60
|
+
|
|
61
|
+
let currentBreakpoint = isXs ? 'xs' : 'unknown' // Default value
|
|
62
|
+
if (isSm) currentBreakpoint = 'sm'
|
|
63
|
+
if (isMd) currentBreakpoint = 'md'
|
|
64
|
+
if (isLg) currentBreakpoint = 'lg'
|
|
65
|
+
if (isXl) currentBreakpoint = 'xl'
|
|
66
|
+
|
|
67
|
+
return currentBreakpoint
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const useMapBreakpoint = <T>(map: Record<string, T>, def?: T, breakpoint?: string): T => {
|
|
71
|
+
const _breakpoint = useBreakPoint()
|
|
72
|
+
breakpoint = breakpoint ?? _breakpoint
|
|
73
|
+
const result = map[breakpoint] ?? def
|
|
74
|
+
if (result == null) {
|
|
75
|
+
throw new SyntaxError(`Breakpoint should always return value. We have ${breakpoint}, but ${Object.keys(map).join(', ')} are available`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type * from './types.js'
|
|
2
|
+
|
|
3
|
+
export * from './panel-app/index.js'
|
|
4
|
+
export * from './layout/index.js'
|
|
5
|
+
|
|
6
|
+
export * from './form/index.js'
|
|
7
|
+
export * from './form/text/index.js'
|
|
8
|
+
export * from './form/button/index.js'
|
|
9
|
+
export * from './uploader/index.js'
|
|
10
|
+
|
|
11
|
+
export * from './button/index.js'
|
|
12
|
+
|
|
13
|
+
export * from './block.js'
|
|
14
|
+
export * from './text.js'
|
|
15
|
+
export * from './link.js'
|
|
16
|
+
export * from './helper.js'
|
|
17
|
+
export * from './status.js'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
2
|
+
import type { FC } from 'react'
|
|
3
|
+
import type { LinkProps } from './types.js'
|
|
4
|
+
import MUILink from '@mui/material/Link'
|
|
5
|
+
import type { TypographyOwnProps } from '@mui/material'
|
|
6
|
+
import { useValue } from '@owlmeans/client'
|
|
7
|
+
import { useContext } from '@owlmeans/web-client'
|
|
8
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
9
|
+
|
|
10
|
+
export const Link: FC<LinkProps> = ({ src, module, name, variant, children, center, open, styles }) => {
|
|
11
|
+
const t = usePanelI18n()
|
|
12
|
+
const context = useContext()
|
|
13
|
+
|
|
14
|
+
const href = useValue(async () => {
|
|
15
|
+
if (src != null) {
|
|
16
|
+
return src
|
|
17
|
+
}
|
|
18
|
+
if (module != null) {
|
|
19
|
+
module = typeof module === 'string' ? context.module<ClientModule<string>>(module) : module
|
|
20
|
+
const [url] = await module.call<string>()
|
|
21
|
+
|
|
22
|
+
return url
|
|
23
|
+
}
|
|
24
|
+
return null
|
|
25
|
+
}, [src, module])
|
|
26
|
+
|
|
27
|
+
const label = name != null
|
|
28
|
+
? t(name)
|
|
29
|
+
: children != null || module == null
|
|
30
|
+
? undefined : t(`modules.${typeof module == 'string' ? module : module.alias}`)
|
|
31
|
+
const target = open ? '_blank' : undefined
|
|
32
|
+
return <MUILink href={href ?? undefined} target={target} variant={variant as TypographyOwnProps['variant']}
|
|
33
|
+
sx={{ textAlign: center ? 'center' : 'inherit', ...styles }}>{label ?? children}</MUILink>
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FC } from 'react'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import type { PanelAppProps } from './types.js'
|
|
4
|
+
|
|
5
|
+
import { App } from '@owlmeans/client'
|
|
6
|
+
import { I18nContext } from '@owlmeans/client-i18n'
|
|
7
|
+
import CssBaseline from '@mui/material/CssBaseline'
|
|
8
|
+
import { createTheme, ThemeProvider } from '@mui/material/styles'
|
|
9
|
+
|
|
10
|
+
export const PanelApp: FC<PanelAppProps> = ({ context, provide, children, theme }) => {
|
|
11
|
+
theme = useMemo(() => theme ?? createTheme(), [theme])
|
|
12
|
+
return <ThemeProvider theme={theme}>
|
|
13
|
+
<I18nContext config={context.cfg}>
|
|
14
|
+
<App context={context} provide={provide}>
|
|
15
|
+
<CssBaseline />
|
|
16
|
+
{children}
|
|
17
|
+
</App >
|
|
18
|
+
</I18nContext>
|
|
19
|
+
</ThemeProvider>
|
|
20
|
+
}
|