Refactor(web): Replace final-form with Formik and cleanup (#46)

* refactor: begin to replace final-form

* refactor: replace final-form with formik n cleanup
This commit is contained in:
Ludvig Lundgren 2021-12-23 22:01:59 +01:00 committed by GitHub
parent c4d580eb03
commit 5e29564f03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 1523 additions and 3409 deletions

View file

@ -6,20 +6,47 @@ import {
} from "../../domain/interfaces";
import { Dialog, Transition } from "@headlessui/react";
import { XIcon } from "@heroicons/react/solid";
import { classNames } from "../../styles/utils";
import { Form, useField } from "react-final-form";
import { sleep, classNames } from "../../utils";
import { Form, Formik, useFormikContext } from "formik";
import DEBUG from "../../components/debug";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import { queryClient } from "../../App";
import APIClient from "../../api/APIClient";
import { sleep } from "../../utils/utils";
import { DownloadClientTypeOptions } from "../../domain/constants";
import { NumberFieldWide, PasswordFieldWide, RadioFieldsetWide } from "../../components/inputs/wide";
import { toast } from 'react-hot-toast'
import Toast from '../../components/notifications/Toast';
import { useToggle } from "../../hooks/hooks";
import { DeleteModal } from "../../components/modals";
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "../../components/inputs/input_wide";
import { RadioFieldsetWide } from "../../components/inputs/radio";
interface InitialValuesSettings {
basic?: {
auth: boolean;
username: string;
password: string;
};
rules?: {
enabled?: boolean;
ignore_slow_torrents?: boolean;
download_speed_threshold?: number;
max_active_downloads?: number;
};
}
interface InitialValues {
name: string;
type: DOWNLOAD_CLIENT_TYPES;
enabled: boolean;
host: string;
port: number;
ssl: boolean;
username: string;
password: string;
settings: InitialValuesSettings;
}
function FormFieldsDefault() {
return (
@ -29,7 +56,7 @@ function FormFieldsDefault() {
<NumberFieldWide name="port" label="Port" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="ssl" label="SSL" />
<SwitchGroupWide name="ssl" label="SSL" />
</div>
<TextFieldWide name="username" label="Username" />
@ -39,7 +66,10 @@ function FormFieldsDefault() {
}
function FormFieldsArr() {
const { input } = useField("settings.basic.auth");
const {
values: { settings },
} = useFormikContext<InitialValues>();
return (
<Fragment>
<TextFieldWide name="host" label="Host" help="Full url like http(s)://domain.ltd/" />
@ -47,10 +77,10 @@ function FormFieldsArr() {
<PasswordFieldWide name="settings.apikey" label="API key" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="settings.basic.auth" label="Basic auth" />
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
</div>
{input.value === true && (
{settings.basic?.auth === true && (
<Fragment>
<TextFieldWide name="settings.basic.username" label="Username" />
<PasswordFieldWide name="settings.basic.password" label="Password" />
@ -71,7 +101,9 @@ export const componentMap: any = {
function FormFieldsRulesBasic() {
const { input: enabled } = useField("settings.rules.enabled");
const {
values: { settings },
} = useFormikContext<InitialValues>();
return (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
@ -84,10 +116,10 @@ function FormFieldsRulesBasic() {
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="settings.rules.enabled" label="Enabled" />
<SwitchGroupWide name="settings.rules.enabled" label="Enabled" />
</div>
{enabled.value === true && (
{settings && settings.rules?.enabled === true && (
<Fragment>
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads" />
</Fragment>
@ -97,8 +129,9 @@ function FormFieldsRulesBasic() {
}
function FormFieldsRules() {
const { input } = useField("settings.rules.ignore_slow_torrents");
const { input: enabled } = useField("settings.rules.enabled");
const {
values: { settings },
} = useFormikContext<InitialValues>();
return (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
@ -111,17 +144,17 @@ function FormFieldsRules() {
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="settings.rules.enabled" label="Enabled" />
<SwitchGroupWide name="settings.rules.enabled" label="Enabled" />
</div>
{enabled.value === true && (
{settings.rules?.enabled === true && (
<Fragment>
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="settings.rules.ignore_slow_torrents" label="Ignore slow torrents" />
<SwitchGroupWide name="settings.rules.ignore_slow_torrents" label="Ignore slow torrents" />
</div>
{input.value === true && (
{settings.rules?.ignore_slow_torrents === true && (
<Fragment>
<NumberFieldWide name="settings.rules.download_speed_threshold" label="Download speed threshold" placeholder="in KB/s" help="If download speed is below this when max active downloads is hit, download anyways. KB/s" />
</Fragment>
@ -138,6 +171,100 @@ export const rulesComponentMap: any = {
QBITTORRENT: <FormFieldsRules />,
};
interface formButtonsProps {
isSuccessfulTest: boolean;
isErrorTest: boolean;
isTesting: boolean;
cancelFn: any;
testFn: any;
values: any;
type: "CREATE" | "UPDATE";
toggleDeleteModal?: any;
}
function DownloadClientFormButtons({ type, isSuccessfulTest, isErrorTest, isTesting, cancelFn, testFn, values, toggleDeleteModal }: formButtonsProps) {
const test = () => {
testFn(values)
}
return (
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
<div className={classNames(type === "CREATE" ? "justify-end" : "justify-between", "space-x-3 flex")}>
{type === "UPDATE" && (
<button
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
onClick={toggleDeleteModal}
>
Remove
</button>
)}
<div className="flex">
<button
type="button"
className={classNames(
isSuccessfulTest
? "text-green-500 border-green-500 bg-green-50"
: isErrorTest
? "text-red-500 border-red-500 bg-red-50"
: "border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-400 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
isTesting ? "cursor-not-allowed" : "",
"mr-2 inline-flex items-center px-4 py-2 border font-medium rounded-md shadow-sm text-sm transition ease-in-out duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
)}
disabled={isTesting}
// onClick={() => testClient(values)}
onClick={test}
>
{isTesting ? (
<svg
className="animate-spin h-5 w-5 text-green-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
) : isSuccessfulTest ? (
"OK!"
) : isErrorTest ? (
"ERROR"
) : (
"Test"
)}
</button>
<button
type="button"
className="mr-4 bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={cancelFn}
>
Cancel
</button>
<button
type="submit"
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
>
{type === "CREATE" ? "Create" : "Save"}
</button>
</div>
</div>
</div>
)
}
export function DownloadClientAddForm({ isOpen, toggle }: any) {
const [isTesting, setIsTesting] = useState(false);
const [isSuccessfulTest, setIsSuccessfulTest] = useState(false);
@ -197,6 +324,18 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
testClientMutation.mutate(data);
};
let initialValues: InitialValues = {
name: "",
type: DOWNLOAD_CLIENT_TYPES.qBittorrent,
enabled: true,
host: "",
port: 10000,
ssl: false,
username: "",
password: "",
settings: {}
}
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog
@ -220,137 +359,75 @@ export function DownloadClientAddForm({ isOpen, toggle }: any) {
leaveTo="translate-x-full"
>
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
<Form
initialValues={{
name: "",
type: DOWNLOAD_CLIENT_TYPES.qBittorrent,
enabled: true,
host: "",
port: 10000,
ssl: false,
username: "",
password: "",
}}
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
>
{({ handleSubmit, values }) => {
return (
<form
className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}
>
<div className="flex-1">
<div className="px-4 py-6 bg-gray-50 dark:bg-gray-900 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
Add client
</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Add download client.
</p>
</div>
<div className="h-7 flex items-center">
<button
type="button"
className="bg-white dark:bg-gray-800 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon
className="h-6 w-6"
aria-hidden="true"
/>
</button>
</div>
{({ handleSubmit, values }) => (
<Form
className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}
>
<div className="flex-1">
<div className="px-4 py-6 bg-gray-50 dark:bg-gray-900 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
Add client
</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Add download client.
</p>
</div>
</div>
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<TextFieldWide name="name" label="Name" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
<SwitchGroup name="enabled" label="Enabled" />
<div className="h-7 flex items-center">
<button
type="button"
className="bg-white dark:bg-gray-800 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon
className="h-6 w-6"
aria-hidden="true"
/>
</button>
</div>
<RadioFieldsetWide
name="type"
legend="Type"
options={DownloadClientTypeOptions}
/>
<div>{componentMap[values.type]}</div>
</div>
</div>
{rulesComponentMap[values.type]}
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<TextFieldWide name="name" label="Name" />
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
<div className="space-x-3 flex justify-end">
<button
type="button"
className={classNames(
isSuccessfulTest
? "text-green-500 border-green-500 bg-green-50"
: isErrorTest
? "text-red-500 border-red-500 bg-red-50"
: "border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-400 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
isTesting ? "cursor-not-allowed" : "",
"mr-2 inline-flex items-center px-4 py-2 border font-medium rounded-md shadow-sm text-sm transition ease-in-out duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
)}
disabled={isTesting}
onClick={() => testClient(values)}
>
{isTesting ? (
<svg
className="animate-spin h-5 w-5 text-green-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
) : isSuccessfulTest ? (
"OK!"
) : isErrorTest ? (
"ERROR"
) : (
"Test"
)}
</button>
<button
type="button"
className="bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={toggle}
>
Cancel
</button>
<button
type="submit"
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
>
Create
</button>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
</div>
<DEBUG values={values} />
</form>
);
}}
</Form>
<RadioFieldsetWide
name="type"
legend="Type"
options={DownloadClientTypeOptions}
/>
<div>{componentMap[values.type]}</div>
</div>
</div>
{rulesComponentMap[values.type]}
<DownloadClientFormButtons
type="CREATE"
isTesting={isTesting}
isSuccessfulTest={isSuccessfulTest}
isErrorTest={isErrorTest}
cancelFn={toggle}
testFn={testClient}
values={values}
/>
<DEBUG values={values} />
</Form>
)}
</Formik>
</div>
</Transition.Child>
</div>
@ -433,6 +510,19 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
testClientMutation.mutate(data);
};
let initialValues = {
id: client.id,
name: client.name,
type: client.type,
enabled: client.enabled,
host: client.host,
port: client.port,
ssl: client.ssl,
username: client.username,
password: client.password,
settings: client.settings,
}
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog
@ -465,24 +555,13 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
leaveTo="translate-x-full"
>
<div className="w-screen max-w-2xl border-l dark:border-gray-700">
<Form
initialValues={{
id: client.id,
name: client.name,
type: client.type,
enabled: client.enabled,
host: client.host,
port: client.port,
ssl: client.ssl,
username: client.username,
password: client.password,
settings: client.settings,
}}
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
>
{({ handleSubmit, values }) => {
return (
<form
<Form
className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}
>
@ -517,7 +596,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
<TextFieldWide name="name" label="Name" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="enabled" label="Enabled" />
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<RadioFieldsetWide
@ -532,81 +611,22 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) {
{rulesComponentMap[values.type]}
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
<div className="space-x-3 flex justify-between">
<button
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
onClick={toggleDeleteModal}
>
Remove
</button>
<div className="flex">
<button
type="button"
className={classNames(
isSuccessfulTest
? "text-green-500 border-green-500 bg-green-50"
: isErrorTest
? "text-red-500 border-red-500 bg-red-50"
: "border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-400 bg-white dark:bg-gray-700 hover:bg-gray-50 focus:border-rose-700 active:bg-rose-700",
isTesting ? "cursor-not-allowed" : "",
"mr-2 inline-flex items-center px-4 py-2 border font-medium rounded-md shadow-sm text-sm transition ease-in-out duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
)}
disabled={isTesting}
onClick={() => testClient(values)}
>
{isTesting ? (
<svg
className="animate-spin h-5 w-5 text-green-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
) : isSuccessfulTest ? (
"OK!"
) : isErrorTest ? (
"ERROR"
) : (
"Test"
)}
</button>
<DownloadClientFormButtons
type="UPDATE"
toggleDeleteModal={toggleDeleteModal}
isTesting={isTesting}
isSuccessfulTest={isSuccessfulTest}
isErrorTest={isErrorTest}
cancelFn={toggle}
testFn={testClient}
values={values}
/>
<button
type="button"
className="mr-4 bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={toggle}
>
Cancel
</button>
<button
type="submit"
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
>
Create
</button>
</div>
</div>
</div>
<DEBUG values={values} />
</form>
</Form>
);
}}
</Form>
</Formik>
</div>
</Transition.Child>
</div>

View file

@ -1,20 +1,22 @@
import React, { Fragment } from "react";
import { Fragment } from "react";
import { useMutation, useQuery } from "react-query";
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
import { sleep } from "../../utils/utils";
import { sleep } from "../../utils";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, Form } from "react-final-form";
import { Field, FieldProps, Form, Formik } from "formik";
import DEBUG from "../../components/debug";
import Select from "react-select";
import Select, { components, InputProps } from "react-select";
import { queryClient } from "../../App";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide } from "../../components/inputs/input_wide";
import { toast } from 'react-hot-toast'
import Toast from '../../components/notifications/Toast';
import { SlideOver } from "../../components/panels";
const Input = ({ type, ...rest }: InputProps) => <components.Input {...rest} />;
interface AddProps {
isOpen: boolean;
toggle: any;
@ -44,7 +46,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
onSuccess: (data) => {
// console.log("irc mutation: ", data);
// queryClient.invalidateQueries(['indexer']);
// queryClient.invalidateQueries(['networks']);
// sleep(1500)
// toggle()
@ -61,24 +63,24 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
let channels: Channel[] = []
if (ind.irc.channels.length) {
ind.irc.channels.forEach(element => {
channels.push({ name: element })
channels.push({ name: element, password: "" })
});
}
const network: Network = {
name: ind.name,
enabled: false,
server: formData.irc.server,
port: formData.irc.port,
tls: formData.irc.tls,
server: ind.irc.server,
port: ind.irc.port,
tls: ind.irc.tls,
nickserv: formData.irc.nickserv,
invite_command: formData.irc.invite_command,
settings: formData.irc.settings,
channels: channels,
}
console.log("network: ", network);
// console.log("network: ", network);
// console.log("formData: ", formData);
mutation.mutate(formData, {
onSuccess: (data) => {
@ -86,7 +88,6 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
ircMutation.mutate(network)
}
})
};
const renderSettingFields = (indexer: string) => {
@ -109,7 +110,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
return null
})}
<div hidden={true}>
<TextFieldWide name={`name`} label="Name" defaultValue={ind?.name} />
<TextFieldWide name="name" label="Name" defaultValue={ind?.name} />
</div>
</div>
)
@ -141,11 +142,11 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
return null
})}
<div hidden={true}>
<TextFieldWide name={`irc.server`} label="Server" defaultValue={ind.irc.server} />
<NumberFieldWide name={`irc.port`} label="Port" defaultValue={ind.irc.port} />
<SwitchGroup name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
</div>
{/* <div hidden={false}>
<TextFieldWide name="irc.server" label="Server" defaultValue={ind.irc.server} />
<NumberFieldWide name="irc.port" label="Port" defaultValue={ind.irc.port} />
<SwitchGroupWide name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
</div> */}
</div>
)}
</Fragment>
@ -170,18 +171,20 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
leaveTo="translate-x-full"
>
<div className="w-screen max-w-2xl dark:border-gray-700 border-l">
<Form
<Formik
enableReinitialize={true}
initialValues={{
enabled: true,
identifier: "",
irc: {}
irc: {},
settings: {},
}}
onSubmit={onSubmit}
>
{({ handleSubmit, values }) => {
{({ values }) => {
return (
<form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
<Form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll">
<div className="flex-1">
<div className="px-4 py-6 bg-gray-50 dark:bg-gray-900 sm:px-6">
<div className="flex items-start justify-between space-x-3">
@ -206,11 +209,8 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
</div>
</div>
<div
className="py-6 space-y-6 py-0 space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="py-6 space-y-6 py-0 space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label
htmlFor="identifier"
@ -220,28 +220,29 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
</label>
</div>
<div className="sm:col-span-2">
<Field
name="identifier"
parse={val => val && val.value}
format={val => data && data.find((o: any) => o.value === val)}
render={({ input, meta }) => (
<React.Fragment>
<Select {...input}
isClearable={true}
placeholder="Choose an indexer"
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
label: v.name,
value: v.identifier
}))} />
</React.Fragment>
<Field name="identifier" type="select">
{({ field, form: { setFieldValue } }: FieldProps) => (
<Select {...field}
isClearable={true}
isSearchable={true}
components={{ Input }}
placeholder="Choose an indexer"
value={field?.value && field.value.value}
onChange={(option: any) => {
setFieldValue(field.name, option?.value ?? "")
}}
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
label: v.name,
value: v.identifier
}))} />
)}
/>
</Field>
</div>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="enabled" label="Enabled" />
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
@ -272,10 +273,10 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
</div>
<DEBUG values={values} />
</form>
</Form>
)
}}
</Form>
</Formik>
</div>
</Transition.Child>
@ -361,45 +362,38 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
initialValues={initialValues}
>
{({ values }: any) => (
<>
<div
className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
>
Name
</label>
</div>
<Field name="name">
{({ input, meta }) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm dark:bg-gray-800 sm:text-sm dark:text-white focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 dark:border-gray-700 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
>
Name
</label>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
<SwitchGroup name="enabled" label="Enabled" />
</div>
{renderSettingFields(indexer.settings)}
<Field name="name">
{({ field, meta }: FieldProps) => (
<div className="sm:col-span-2">
<input
type="text"
{...field}
className="block w-full shadow-sm dark:bg-gray-800 sm:text-sm dark:text-white focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 dark:border-gray-700 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
</div>
</>
)}
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
{renderSettingFields(indexer.settings)}
</div>
)}
</SlideOver>
)
}

View file

@ -1,19 +1,83 @@
import { useMutation } from "react-query";
import { Network } from "../../domain/interfaces";
import { Channel, Network } from "../../domain/interfaces";
import { XIcon } from "@heroicons/react/solid";
import { Field } from "react-final-form";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import { queryClient } from "../../App";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import { Field, FieldArray, FieldProps } from "formik";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
import { TextFieldWide, PasswordFieldWide, SwitchGroupWide, NumberFieldWide } from "../../components/inputs/input_wide";
import { toast } from 'react-hot-toast';
import Toast from '../../components/notifications/Toast';
import { SlideOver } from "../../components/panels";
function ChannelsFieldArray({ values }: any) {
return (
<div className="p-6">
<FieldArray name="channels">
{({ remove, push }) => (
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
{values && values.channels.length > 0 ? (
values.channels.map((_channel: Channel, index: number) => (
<div key={index} className="flex justify-between">
<div className="flex">
<Field name={`channels.${index}.name`}>
{({ field }: FieldProps) => (
<input
{...field}
type="text"
value={field.value ?? ""}
onChange={field.onChange}
placeholder="#Channel"
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
/>
)}
</Field>
<Field name={`channels.${index}.password`}>
{({ field }: FieldProps) => (
<input
{...field}
type="text"
value={field.value ?? ""}
onChange={field.onChange}
placeholder="Password"
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
/>
)}
</Field>
</div>
<button
type="button"
className="bg-white dark:bg-gray-700 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={() => remove(index)}
>
<span className="sr-only">Remove</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
))
) : (
<span className="text-center text-sm text-grey-darker dark:text-white">
No channels!
</span>
)}
<button
type="button"
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 rounded self-center text-center"
onClick={() => push({ name: "", password: "" })}
>
Add Channel
</button>
</div>
)}
</FieldArray>
</div>
)
}
export function IrcNetworkAddForm({ isOpen, toggle }: any) {
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
onSuccess: (data) => {
@ -66,15 +130,13 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
name: "",
enabled: true,
server: "",
port: 6667,
tls: false,
pass: "",
nickserv: {
account: ""
}
}
const mutators = {
...arrayMutators
},
channels: [],
}
return (
@ -85,19 +147,16 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
toggle={toggle}
onSubmit={onSubmit}
initialValues={initialValues}
mutators={mutators}
validate={validate}
>
{() => (
{(values) => (
<>
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
<SwitchGroup name="enabled" label="Enabled" />
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<div>
@ -105,7 +164,7 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="tls" label="TLS" />
<SwitchGroupWide name="tls" label="TLS" />
</div>
<PasswordFieldWide name="pass" label="Password" help="Network password" />
@ -117,57 +176,7 @@ export function IrcNetworkAddForm({ isOpen, toggle }: any) {
</div>
</div>
<div className="p-6">
<FieldArray name="channels">
{({ fields }) => (
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
{fields && (fields.length as any) > 0 ? (
fields.map((name, index) => (
<div key={name} className="flex justify-between">
<div className="flex">
<Field
name={`${name}.name`}
component="input"
type="text"
placeholder="#Channel"
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
/>
<Field
name={`${name}.password`}
component="input"
type="text"
placeholder="Password"
className="dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
/>
</div>
<button
type="button"
className="bg-white dark:bg-gray-700 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={() => fields.remove(index)}
>
<span className="sr-only">Remove</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
))
) : (
<span className="text-center text-sm text-grey-darker dark:text-white">
No channels!
</span>
)}
<button
type="button"
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 rounded self-center text-center"
onClick={() => fields.push({ name: "", password: "" })}
>
Add Channel
</button>
</div>
)}
</FieldArray>
</div>
<ChannelsFieldArray values={values} />
</>
)}
</SlideOver>
@ -193,8 +202,6 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
})
const onSubmit = (data: any) => {
console.log(data)
// easy way to split textarea lines into array of strings for each newline.
// parse on the field didn't really work.
// TODO fix connect_commands on network update
@ -241,14 +248,9 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
nickserv: network.nickserv,
pass: network.pass,
invite_command: network.invite_command,
// connect_commands: network.connect_commands,
channels: network.channels
}
const mutators = {
...arrayMutators
}
return (
<SlideOver
type="UPDATE"
@ -258,18 +260,16 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
onSubmit={onSubmit}
deleteAction={deleteAction}
initialValues={initialValues}
mutators={mutators}
validate={validate}
>
{() => (
{(values) => (
<>
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0">
<SwitchGroup name="enabled" label="Enabled" />
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<div>
@ -277,7 +277,7 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="tls" label="TLS" />
<SwitchGroupWide name="tls" label="TLS" />
</div>
<PasswordFieldWide name="pass" label="Password" help="Network password" />
@ -289,57 +289,7 @@ export function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
</div>
</div>
<div className="p-6">
<FieldArray name="channels">
{({ fields }) => (
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
{fields && (fields.length as any) > 0 ? (
fields.map((name, index) => (
<div key={name} className="flex justify-between">
<div className="flex">
<Field
name={`${name}.name`}
component="input"
type="text"
placeholder="#Channel"
className="mr-4 dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
/>
<Field
name={`${name}.password`}
component="input"
type="text"
placeholder="Password"
className="dark:bg-gray-700 focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-600 block w-full shadow-sm sm:text-sm dark:text-white rounded-md"
/>
</div>
<button
type="button"
className="bg-white dark:bg-gray-700 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
onClick={() => fields.remove(index)}
>
<span className="sr-only">Remove</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
))
) : (
<span className="text-center text-sm text-grey-darker dark:text-white">
No channels!
</span>
)}
<button
type="button"
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500 rounded self-center text-center"
onClick={() => fields.push({ name: "", password: "" })}
>
Add Channel
</button>
</div>
)}
</FieldArray>
</div>
<ChannelsFieldArray values={values} />
</>
)}
</SlideOver>