diff --git a/.env.example b/.env.example index 8cdf80f91..db8aaac59 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ # should be updated accordingly. # Database -DATABASE_URL="file:../database/db.sqlite" +DATABASE_URL="file:./database/db.sqlite" # Next Auth # You can generate a new secret on the command line with: diff --git a/src/components/Board/BoardCreateModal.tsx b/src/components/Board/BoardCreateModal.tsx new file mode 100644 index 000000000..e3a4505d8 --- /dev/null +++ b/src/components/Board/BoardCreateModal.tsx @@ -0,0 +1,106 @@ +import { ActionIcon, Button, Group, Loader, Stack, Switch, TextInput } from '@mantine/core'; +import { useForm, zodResolver } from '@mantine/form'; +import { useDebouncedValue } from '@mantine/hooks'; +import { ContextModalProps } from '@mantine/modals'; +import { IconAlertTriangle, IconCheck, IconPencil, IconPencilOff } from '@tabler/icons-react'; +import { useRouter } from 'next/router'; +import { useState } from 'react'; +import { z } from 'zod'; +import { api } from '~/utils/api'; +import { createBoardSchema } from '~/validations/boards'; + +export const CreateBoardModal = ({ id, context }: ContextModalProps>) => { + const [autoBoardName, setAutoBoardName] = useState(true); + const router = useRouter(); + const form = useForm({ + validate: zodResolver(createBoardSchema), + validateInputOnBlur: true, + initialValues: { + pageTitle: '', + boardName: '', + allowGuests: false, + }, + }); + const [debouncedRouteName] = useDebouncedValue(form.values.boardName, 500); + const { data: boardNameAvailable, isFetching } = api.boards.checkNameAvailable.useQuery( + { boardName: debouncedRouteName }, + { enabled: !!debouncedRouteName } + ); + const { mutate: createBoard, isLoading, isSuccess } = api.boards.create.useMutation(); + const utils = api.useContext(); + + const handleSubmit = (values: FormType) => { + if (!boardNameAvailable) return; + createBoard(values, { + onSuccess: async () => { + await router.push(`/board/${values.boardName}`); + context.closeModal(id); + utils.boards.checkNameAvailable.invalidate({ boardName: values.boardName }); + }, + }); + }; + + return ( +
+ + { + form.getInputProps('pageTitle').onChange(e); + if (!autoBoardName) return; + form.setFieldValue( + 'boardName', + e.currentTarget.value + .replace(/([A-z0-9])[^A-z0-9]+([A-z0-9])/g, '$1-$2') + .replace(/([A-z0-9])[^A-z0-9\-]+([A-z0-9])/g, '$1-$2') + .replace(/[^A-z\-0-9]+/g, '') + .replace(/-+/g, '-') + .toLowerCase() + ); + }} + /> + setAutoBoardName((c) => !c)}> + {autoBoardName ? ( + + ) : ( + + )} + + } + icon={ + debouncedRouteName !== form.values.boardName || isFetching ? ( + + ) : boardNameAvailable === true ? ( + + ) : boardNameAvailable === false ? ( + + ) : null + } + /> + + + + + + +
+ ); +}; + +type FormType = z.infer; diff --git a/src/components/Board/Items/App/Edit/AppereanceTab.tsx b/src/components/Board/Items/App/Edit/AppereanceTab.tsx index 3752fa055..6d61730c5 100644 --- a/src/components/Board/Items/App/Edit/AppereanceTab.tsx +++ b/src/components/Board/Items/App/Edit/AppereanceTab.tsx @@ -39,7 +39,7 @@ export const AppearanceTab = ({ }, [debouncedValue]); return ( - + { const { t } = useTranslation('layout/modals/add-app'); return ( - + void; -} - -export const GenericSecretInput = ({ - label, - value, - setIcon, - secretIsPresent, - type, - onClickUpdateButton, - ...props -}: GenericSecretInputProps) => { - const { classes } = useStyles(); - - const Icon = setIcon; - - const [displayUpdateField, setDisplayUpdateField] = useState(!secretIsPresent); - const { t } = useTranslation(['layout/modals/add-app', 'common']); - - return ( - - - - - - - - - - - {t(label)} - - - - - {secretIsPresent - ? t('integration.type.defined') - : t('integration.type.undefined')} - - {type === 'private' ? ( - - - {t('integration.type.private')} - - - ) : ( - - - {t('integration.type.public')} - - - )} - - - - {type === 'private' - ? 'Private: Once saved, you cannot read out this value again' - : 'Public: Can be read out repeatedly'} - - - - - - - {displayUpdateField === true ? ( - - ) : ( - - )} - - - - - ); -}; - -const useStyles = createStyles(() => ({ - subtitle: { - lineHeight: 1.1, - }, - alignSelfCenter: { - alignSelf: 'center', - }, - textTransformUnset: { - textTransform: 'inherit', - }, -})); diff --git a/src/components/Board/Items/App/Edit/IntegrationTab/InputElements/IntegrationSelector.tsx b/src/components/Board/Items/App/Edit/IntegrationTab/InputElements/IntegrationSelector.tsx deleted file mode 100644 index 82574b82a..000000000 --- a/src/components/Board/Items/App/Edit/IntegrationTab/InputElements/IntegrationSelector.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -import { Group, Image, Select, SelectItem, Text } from '@mantine/core'; -import { UseFormReturnType } from '@mantine/form'; -import { useTranslation } from 'next-i18next'; -import { forwardRef } from 'react'; - -import { - AppIntegrationPropertyType, - AppIntegrationType, - AppType, - IntegrationField, - integrationFieldDefinitions, - integrationFieldProperties, -} from '~/types/app'; - -interface IntegrationSelectorProps { - form: UseFormReturnType AppType>; -} - -export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => { - const { t } = useTranslation('layout/modals/add-app'); - - const data = availableIntegrations.filter((x) => - Object.keys(integrationFieldProperties).includes(x.value) - ); - - const getNewProperties = (value: string | null): AppIntegrationPropertyType[] => { - if (!value) return []; - const integrationType = value as Exclude; - if (integrationType === null) { - return []; - } - - const requiredProperties = Object.entries(integrationFieldDefinitions).filter(([k, v]) => { - const val = integrationFieldProperties[integrationType]; - return val.includes(k as IntegrationField); - })!; - return requiredProperties.map(([k, value]) => ({ - type: value.type, - field: k as IntegrationField, - value: undefined, - isDefined: false, - })); - }; - - const inputProps = form.getInputProps('integration.type'); - - return ( -