feat: Introduce InputCreator

This commit is contained in:
Ali BARIN
2021-10-11 23:22:12 +02:00
parent 981ea6d163
commit f50c09ed37
13 changed files with 196 additions and 15 deletions

View File

@@ -5,6 +5,7 @@ const appType = new GraphQLObjectType({
name: 'App', name: 'App',
fields: { fields: {
name: { type: GraphQLString }, name: { type: GraphQLString },
key: { type: GraphQLString },
iconUrl: { type: GraphQLString }, iconUrl: { type: GraphQLString },
docUrl: { type: GraphQLString }, docUrl: { type: GraphQLString },
primaryColor: { type: GraphQLString }, primaryColor: { type: GraphQLString },

View File

@@ -8,6 +8,7 @@ const fieldType = new GraphQLObjectType({
type: { type: GraphQLString }, type: { type: GraphQLString },
required: { type: GraphQLBoolean}, required: { type: GraphQLBoolean},
readOnly: { type: GraphQLBoolean}, readOnly: { type: GraphQLBoolean},
value: { type: GraphQLString},
placeholder: { type: GraphQLString}, placeholder: { type: GraphQLString},
description: { type: GraphQLString}, description: { type: GraphQLString},
docUrl: { type: GraphQLString}, docUrl: { type: GraphQLString},

View File

@@ -16,6 +16,7 @@
"@types/react": "^17.0.0", "@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0", "@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.3.0", "@types/react-router-dom": "^5.3.0",
"clipboard-copy": "^4.0.1",
"graphql": "^15.6.0", "graphql": "^15.6.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@@ -1,25 +1,54 @@
import { useMutation } from '@apollo/client';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText'; import DialogContentText from '@mui/material/DialogContentText';
import Dialog from '@mui/material/Dialog'; import Dialog from '@mui/material/Dialog';
import Button from '@mui/material/Button';
import { FieldValues, SubmitHandler } from 'react-hook-form';
import InputCreator from 'components/InputCreator';
import { CREATE_CREDENTIALS } from 'graphql/mutations/create-credentials';
import type { App } from 'types/app'; import type { App } from 'types/app';
import { Form } from './style';
type AddAppConnectionProps = { type AddAppConnectionProps = {
onClose: (value: string) => void; onClose: () => void;
application: App; application: App;
}; };
export default function AddAppConnection(props: AddAppConnectionProps){ export default function AddAppConnection(props: AddAppConnectionProps){
const { application, onClose } = props; const { application, onClose } = props;
const { name } = application; const { name, fields } = application;
const [createCredentials, { data: newCredentials }] = useMutation(CREATE_CREDENTIALS);
console.log('newCredentials', newCredentials)
const submitHandler: SubmitHandler<FieldValues> = (data) => {
const variables = {
key: application.key,
displayName: data.displayName,
data: {
consumerKey: data.consumerKey,
consumerSecret: data.consumerSecret
}
};
createCredentials({ variables });
onClose?.();
};
return ( return (
<Dialog open={true} onClose={onClose}> <Dialog open={true} onClose={onClose}>
<DialogTitle>Add connection</DialogTitle> <DialogTitle>Add connection</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText tabIndex={-1}> <DialogContentText tabIndex={-1} component="div">
Add a connection to {name} <Form onSubmit={submitHandler}>
{fields?.map(field => (<InputCreator key={field.key} schema={field} />))}
<Button type="submit" variant="contained" color="primary">Submit</Button>
</Form>
</DialogContentText> </DialogContentText>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -0,0 +1,9 @@
import { styled } from "@mui/material/styles";
import BaseForm from 'components/Form';
export const Form = styled(BaseForm)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
paddingTop: theme.spacing(1),
}));

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { FormProvider, useForm, FieldValues, SubmitHandler, UseFormReturn } from "react-hook-form";
type FormProps = {
children: React.ReactNode;
onSubmit: SubmitHandler<FieldValues>;
}
export default function Form(props: FormProps) {
const { children, onSubmit, ...formProps } = props;
const methods: UseFormReturn = useForm();
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} {...formProps}>
{children}
</form>
</FormProvider>
);
};

View File

@@ -0,0 +1,47 @@
import type { AppFields } from 'types/app';
import { useFormContext } from "react-hook-form";
import TextField from 'components/TextField';
type InputCreatorProps = {
onChange?: React.ChangeEventHandler;
schema: AppFields;
};
export default function InputCreator(props: InputCreatorProps) {
const {
onChange,
schema,
} = props;
const { control } = useFormContext();
const {
key: name,
label,
type,
required,
readOnly,
value,
description,
docUrl,
clickToCopy,
} = schema;
return (
<TextField
defaultValue={value}
required={required}
placeholder=""
disabled={readOnly}
readOnly={readOnly}
onChange={onChange}
name={name}
label={label}
fullWidth
helperText={description}
control={control}
clickToCopy={clickToCopy}
/>
);
};

View File

@@ -1,20 +1,68 @@
import React from "react"; import { useRef } from "react";
import { Controller, Control, FieldValues } from "react-hook-form"; import { Controller, Control, FieldValues } from "react-hook-form";
import MuiTextField from "@mui/material/TextField"; import MuiTextField, { TextFieldProps as MuiTextFieldProps } from "@mui/material/TextField";
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import copyInputValue from 'helpers/copyInputValue';
type TextFieldProps = { type TextFieldProps = {
control: Control<FieldValues>; control?: Control<FieldValues>;
shouldUnregister?: boolean;
name: string; name: string;
clickToCopy?: boolean;
readOnly?: boolean;
} & MuiTextFieldProps;
const createCopyAdornment = (ref: React.RefObject<HTMLInputElement | null>) => {
return (
<InputAdornment position="end">
<IconButton
onClick={() => copyInputValue(ref.current as HTMLInputElement)}
edge="end"
>
<ContentCopyIcon />
</IconButton>
</InputAdornment>
);
} }
export default function TextField({ control, name }: TextFieldProps) { export default function TextField(props: TextFieldProps) {
const inputRef = useRef<HTMLInputElement | null>(null);
const {
control,
required,
name,
defaultValue,
shouldUnregister,
clickToCopy,
readOnly,
...textFieldProps
} = props;
return ( return (
<Controller <Controller
name="MyCheckbox" rules={{ required }}
name={name}
defaultValue={defaultValue || ''}
control={control} control={control}
defaultValue={false} shouldUnregister={shouldUnregister}
rules={{ required: true }} render={({ field: { ref, ...field } }) => (
render={({ field }) => <MuiTextField {...field} />} <MuiTextField
{...textFieldProps}
{...field}
inputRef={(element) => { inputRef.current = element; ref(element); }}
InputProps={{ readOnly, endAdornment: clickToCopy ? createCopyAdornment(inputRef) : null}}
/>
)}
/> />
); );
}; };
TextField.defaultProps = {
readOnly: false,
disabled: false,
clickToCopy: false,
shouldUnregister: false,
};

View File

@@ -0,0 +1,14 @@
import { gql } from '@apollo/client';
export const CREATE_CREDENTIALS = gql`
mutation CreateCredentials($displayName: String!, $key: String!, $data: twitterCredentialInput!) {
createCredential(displayName: $displayName, key: $key, data: $data) {
key
displayName
data {
consumerKey
consumerSecret
}
}
}
`;

View File

@@ -4,6 +4,7 @@ export const GET_APP = gql`
query GetApp($name: String!) { query GetApp($name: String!) {
getApp (name: $name) { getApp (name: $name) {
name name
key
iconUrl iconUrl
docUrl docUrl
primaryColor primaryColor
@@ -13,7 +14,7 @@ export const GET_APP = gql`
type type
required required
readOnly readOnly
placeholder value
description description
docUrl docUrl
clickToCopy clickToCopy

View File

@@ -0,0 +1,4 @@
import copy from 'clipboard-copy';
export default function copyInputValue(element: HTMLInputElement) {
copy(element.value);
};

View File

@@ -4,18 +4,19 @@ type AppFields = {
type: string; type: string;
required: boolean, required: boolean,
readOnly: boolean, readOnly: boolean,
placeholder: string; value: string;
description: string; description: string;
docUrl: string; docUrl: string;
clickToCopy: boolean, clickToCopy: boolean,
}; };
type App = { type App = {
key: string;
name: string; name: string;
iconUrl: string; iconUrl: string;
docUrl: string; docUrl: string;
primaryColor: string; primaryColor: string;
fields: AppFields; fields: AppFields[];
}; };
export type { App, AppFields }; export type { App, AppFields };

View File

@@ -5094,6 +5094,11 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
clipboard-copy@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-4.0.1.tgz#326ef9726d4ffe72d9a82a7bbe19379de692017d"
integrity sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng==
cliui@^5.0.0: cliui@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"