feat: Introduce InputCreator
This commit is contained in:
@@ -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 },
|
||||||
|
@@ -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},
|
||||||
|
@@ -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",
|
||||||
|
@@ -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>
|
||||||
|
9
packages/web/src/components/AddAppConnection/style.ts
Normal file
9
packages/web/src/components/AddAppConnection/style.ts
Normal 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),
|
||||||
|
}));
|
20
packages/web/src/components/Form/index.tsx
Normal file
20
packages/web/src/components/Form/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
47
packages/web/src/components/InputCreator/index.tsx
Normal file
47
packages/web/src/components/InputCreator/index.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@@ -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,
|
||||||
|
};
|
||||||
|
14
packages/web/src/graphql/mutations/create-credentials.ts
Normal file
14
packages/web/src/graphql/mutations/create-credentials.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@@ -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
|
||||||
|
4
packages/web/src/helpers/copyInputValue.ts
Normal file
4
packages/web/src/helpers/copyInputValue.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import copy from 'clipboard-copy';
|
||||||
|
export default function copyInputValue(element: HTMLInputElement) {
|
||||||
|
copy(element.value);
|
||||||
|
};
|
@@ -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 };
|
||||||
|
@@ -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"
|
||||||
|
Reference in New Issue
Block a user