Path: blob/develop/resources/scripts/components/dashboard/search/SearchModal.tsx
7453 views
import { useEffect, useRef, useState } from 'react';1import Modal, { RequiredModalProps } from '@/components/elements/Modal';2import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik';3import { Actions, useStoreActions, useStoreState } from 'easy-peasy';4import { object, string } from 'yup';5import debounce from 'debounce';6import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';7import InputSpinner from '@/components/elements/InputSpinner';8import getServers from '@/api/getServers';9import { Server } from '@/api/server/getServer';10import { ApplicationStore } from '@/state';11import { Link } from 'react-router-dom';12import styled from 'styled-components';13import tw from 'twin.macro';14import Input from '@/components/elements/Input';15import { ip } from '@/lib/formatters';1617type Props = RequiredModalProps;1819interface Values {20term: string;21}2223const ServerResult = styled(Link)`24${tw`flex items-center bg-neutral-900 p-4 rounded border-l-4 border-neutral-900 no-underline transition-all duration-150`};2526&:hover {27${tw`shadow border-cyan-500`};28}2930&:not(:last-of-type) {31${tw`mb-2`};32}33`;3435const SearchWatcher = () => {36const { values, submitForm } = useFormikContext<Values>();3738useEffect(() => {39if (values.term.length >= 3) {40submitForm();41}42}, [values.term]);4344return null;45};4647export default ({ ...props }: Props) => {48const ref = useRef<HTMLInputElement>(null);49const isAdmin = useStoreState(state => state.user.data!.rootAdmin);50const [servers, setServers] = useState<Server[]>([]);51const { clearAndAddHttpError, clearFlashes } = useStoreActions(52(actions: Actions<ApplicationStore>) => actions.flashes,53);5455const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers<Values>) => {56clearFlashes('search');5758// if (ref.current) ref.current.focus();59getServers({ query: term, type: isAdmin ? 'admin-all' : undefined })60.then(servers => setServers(servers.items.filter((_, index) => index < 5)))61.catch(error => {62console.error(error);63clearAndAddHttpError({ key: 'search', error });64})65.then(() => setSubmitting(false))66.then(() => ref.current?.focus());67}, 500);6869useEffect(() => {70if (props.visible) {71if (ref.current) ref.current.focus();72}73}, [props.visible]);7475// Formik does not support an innerRef on custom components.76const InputWithRef = (props: any) => <Input autoFocus {...props} ref={ref} />;7778return (79<Formik80onSubmit={search}81validationSchema={object().shape({82term: string().min(3, 'Please enter at least three characters to begin searching.'),83})}84initialValues={{ term: '' } as Values}85>86{({ isSubmitting }) => (87<Modal {...props}>88<Form>89<FormikFieldWrapper90name={'term'}91label={'Search term'}92description={'Enter a server name, uuid, or allocation to begin searching.'}93>94<SearchWatcher />95<InputSpinner visible={isSubmitting}>96<Field as={InputWithRef} name={'term'} />97</InputSpinner>98</FormikFieldWrapper>99</Form>100{servers.length > 0 && (101<div css={tw`mt-6`}>102{servers.map(server => (103<ServerResult104key={server.uuid}105to={`/server/${server.id}`}106onClick={() => props.onDismissed()}107>108<div css={tw`flex-1 mr-4`}>109<p css={tw`text-sm`}>{server.name}</p>110<p css={tw`mt-1 text-xs text-neutral-400`}>111{server.allocations112.filter(alloc => alloc.isDefault)113.map(allocation => (114<span key={allocation.ip + allocation.port.toString()}>115{allocation.alias || ip(allocation.ip)}:{allocation.port}116</span>117))}118</p>119</div>120<div css={tw`flex-none text-right`}>121<span css={tw`text-xs py-1 px-2 bg-cyan-800 text-cyan-100 rounded`}>122{server.node}123</span>124</div>125</ServerResult>126))}127</div>128)}129</Modal>130)}131</Formik>132);133};134135136