feat: add form error icon for tabs

This commit is contained in:
Manuel Ruwe
2022-12-08 20:42:12 +01:00
parent be8f9f0dc7
commit afbf2d2d2f
4 changed files with 92 additions and 34 deletions

View File

@@ -32,16 +32,16 @@
"@dnd-kit/utilities": "^3.2.0", "@dnd-kit/utilities": "^3.2.0",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.10.5",
"@emotion/server": "^11.10.0", "@emotion/server": "^11.10.0",
"@mantine/carousel": "^5.9.0", "@mantine/carousel": "^5.9.2",
"@mantine/core": "^5.9.0", "@mantine/core": "^5.9.2",
"@mantine/dates": "^5.9.0", "@mantine/dates": "^5.9.2",
"@mantine/dropzone": "^5.9.0", "@mantine/dropzone": "^5.9.2",
"@mantine/form": "^5.9.0", "@mantine/form": "^5.9.2",
"@mantine/hooks": "^5.9.0", "@mantine/hooks": "^5.9.2",
"@mantine/modals": "^5.9.0", "@mantine/modals": "^5.9.2",
"@mantine/next": "^5.9.0", "@mantine/next": "^5.9.2",
"@mantine/notifications": "^5.9.0", "@mantine/notifications": "^5.9.2",
"@mantine/prism": "^5.9.0", "@mantine/prism": "^5.9.2",
"@nivo/core": "^0.79.0", "@nivo/core": "^0.79.0",
"@nivo/line": "^0.79.1", "@nivo/line": "^0.79.1",
"@tabler/icons": "^1.106.0", "@tabler/icons": "^1.106.0",

View File

@@ -1,8 +1,15 @@
import { Alert, Button, createStyles, Group, Stack, Tabs, Text } from '@mantine/core'; import { Alert, Button, createStyles, Group, Stack, Tabs, Text, ThemeIcon } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { ContextModalProps } from '@mantine/modals'; import { ContextModalProps } from '@mantine/modals';
import { hideNotification, showNotification } from '@mantine/notifications'; import { hideNotification, showNotification } from '@mantine/notifications';
import { IconAccessPoint, IconAdjustments, IconBrush, IconClick, IconPlug } from '@tabler/icons'; import {
IconAccessPoint,
IconAdjustments,
IconAlertTriangle,
IconBrush,
IconClick,
IconPlug,
} from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import Image from 'next/image'; import Image from 'next/image';
import { useState } from 'react'; import { useState } from 'react';
@@ -44,17 +51,27 @@ export const EditServiceModal = ({
return null; return null;
}, },
appearance: (appearance) => (!appearance.iconUrl ? 'Icon is required' : null), appearance: {
behaviour: (behaviour) => { iconUrl: (url: string) => {
if (behaviour.onClickUrl === undefined || behaviour.onClickUrl.length < 1) { if (url.length < 1) {
return 'This field is required';
}
return null; return null;
} },
},
behaviour: {
onClickUrl: (url: string) => {
if (url === undefined || url.length < 1) {
return null;
}
if (!behaviour.onClickUrl?.match(serviceUrlRegex)) { if (!url.match(serviceUrlRegex)) {
return 'Uri override is not a valid uri'; return 'Uri override is not a valid uri';
} }
return null; return null;
},
}, },
}, },
validateInputOnChange: true, validateInputOnChange: true,
@@ -102,6 +119,18 @@ export const EditServiceModal = ({
context.closeModal(id); context.closeModal(id);
}; };
const validationErrors = Object.keys(form.errors);
const ValidationErrorIndicator = ({ keys }: { keys: string[] }) => {
const relevantErrors = validationErrors.filter((x) => keys.includes(x));
return (
<ThemeIcon opacity={relevantErrors.length === 0 ? 0 : 1} color="red" variant="light">
<IconAlertTriangle size={15} />
</ThemeIcon>
);
};
return ( return (
<> <>
{configName === undefined || {configName === undefined ||
@@ -130,6 +159,7 @@ export const EditServiceModal = ({
{form.values.name ?? 'New Service'} {form.values.name ?? 'New Service'}
</Text> </Text>
</Stack> </Stack>
<form onSubmit={form.onSubmit(onSubmit)}> <form onSubmit={form.onSubmit(onSubmit)}>
<Tabs <Tabs
value={activeTab} value={activeTab}
@@ -137,19 +167,39 @@ export const EditServiceModal = ({
defaultValue="general" defaultValue="general"
> >
<Tabs.List grow> <Tabs.List grow>
<Tabs.Tab value="general" icon={<IconAdjustments size={14} />}> <Tabs.Tab
rightSection={<ValidationErrorIndicator keys={['name', 'url']} />}
icon={<IconAdjustments size={14} />}
value="general"
>
General General
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="behaviour" icon={<IconClick size={14} />}> <Tabs.Tab
rightSection={<ValidationErrorIndicator keys={['behaviour.onClickUrl']} />}
icon={<IconClick size={14} />}
value="behaviour"
>
Behaviour Behaviour
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="network" icon={<IconAccessPoint size={14} />}> <Tabs.Tab
rightSection={<ValidationErrorIndicator keys={[]} />}
icon={<IconAccessPoint size={14} />}
value="network"
>
Network Network
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="appearance" icon={<IconBrush size={14} />}> <Tabs.Tab
rightSection={<ValidationErrorIndicator keys={['appearance.iconUrl']} />}
icon={<IconBrush size={14} />}
value="appearance"
>
Appearance Appearance
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="integration" icon={<IconPlug size={14} />}> <Tabs.Tab
rightSection={<ValidationErrorIndicator keys={[]} />}
icon={<IconPlug size={14} />}
value="integration"
>
Integration Integration
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>
@@ -162,10 +212,10 @@ export const EditServiceModal = ({
</Tabs> </Tabs>
<Group position="right" mt={100}> <Group position="right" mt={100}>
<Button px={50} variant="light" color="gray" onClick={tryCloseModal}> <Button onClick={tryCloseModal} px={50} variant="light" color="gray">
Cancel Cancel
</Button> </Button>
<Button type="submit" px={50}> <Button disabled={!form.isValid()} px={50} type="submit">
Save Save
</Button> </Button>
</Group> </Group>

View File

@@ -1,6 +1,6 @@
import Image from 'next/image';
import { createStyles, Flex, Tabs, TextInput } from '@mantine/core'; import { createStyles, Flex, Tabs, TextInput } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { IconPhoto } from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { ServiceType } from '../../../../../../types/service'; import { ServiceType } from '../../../../../../types/service';
import { IconSelector } from './IconSelector/IconSelector'; import { IconSelector } from './IconSelector/IconSelector';
@@ -13,14 +13,22 @@ export const AppearanceTab = ({ form }: AppearanceTabProps) => {
const { t } = useTranslation(''); const { t } = useTranslation('');
const { classes } = useStyles(); const { classes } = useStyles();
const Image = () => { const PreviewImage = () => {
if (form.values.appearance.iconUrl !== undefined) { if (form.values.appearance.iconUrl !== undefined && form.values.appearance.iconUrl.length > 0) {
// disabled due to too many dynamic targets for next image cache // disabled due to too many dynamic targets for next image cache
// eslint-disable-next-line @next/next/no-img-element // eslint-disable-next-line @next/next/no-img-element
return <img className={classes.iconImage} src={form.values.appearance.iconUrl} alt="jife" />; return <img className={classes.iconImage} src={form.values.appearance.iconUrl} alt="" />;
} }
return <IconPhoto />; return (
<Image
src="/imgs/logo/logo.png"
width={20}
height={20}
objectFit="contain"
alt=""
/>
);
}; };
return ( return (
@@ -29,7 +37,7 @@ export const AppearanceTab = ({ form }: AppearanceTabProps) => {
<TextInput <TextInput
defaultValue={form.values.appearance.iconUrl} defaultValue={form.values.appearance.iconUrl}
className={classes.textInput} className={classes.textInput}
icon={<Image />} icon={<PreviewImage />}
label="Service Icon" label="Service Icon"
description="Logo of your service displayed in your dashboard. Must return a body content containg an image" description="Logo of your service displayed in your dashboard. Must return a body content containg an image"
variant="default" variant="default"

View File

@@ -78,7 +78,7 @@ export const IconSelector = ({ onChange }: IconSelectorProps) => {
<Flex gap={4} wrap="wrap" pr={15}> <Flex gap={4} wrap="wrap" pr={15}>
{slicedFilteredItems.map((item) => ( {slicedFilteredItems.map((item) => (
<ActionIcon onClick={() => onChange(item)} size={40} p={3}> <ActionIcon onClick={() => onChange(item)} size={40} p={3}>
<img className={classes.icon} src={item.url} alt="icon from repository" /> <img className={classes.icon} src={item.url} alt="" />
</ActionIcon> </ActionIcon>
))} ))}
</Flex> </Flex>