Skip to content

Commit

Permalink
feat(ui, webserver): add general settings (#1405)
Browse files Browse the repository at this point in the history
* feat(ui): init general settings

[autofix.ci] apply automated fixes

feat: useFieldArray

fix: format

fix: text

* fix: Separator

* [autofix.ci] apply automated fixes

* update

* fix: domain list keydown

* update

* update schema.graphql

* connect network form

* connect security form

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes (attempt 3/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Meng Zhang <[email protected]>
  • Loading branch information
3 people authored Feb 8, 2024
1 parent 4c193d4 commit 23661f7
Show file tree
Hide file tree
Showing 12 changed files with 552 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'

import { cn } from '@/lib/utils'

interface GeneralFormSectionProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
title: string
}

export const GeneralFormSection: React.FC<GeneralFormSectionProps> = ({
title,
className,
children,
...props
}) => {
return (
<div className={cn('lg:flex', className)} {...props}>
<div className="text-left lg:w-1/5">
<h1 className="text-2xl font-bold">{title}</h1>
</div>
<div className="flex-1 lg:px-4">
<div className="mb-7 mt-4 lg:mt-0">{children}</div>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client'

import React from 'react'

import { Separator } from '@/components/ui/separator'
import { ListSkeleton } from '@/components/skeleton'

import { GeneralFormSection } from './form-section'
import { GeneralNetworkForm } from './network-form'
import { GeneralSecurityForm } from './security-form'

export default function General() {
// todo usequery

const [initialized, setInitialized] = React.useState(false)

React.useEffect(() => {
setTimeout(() => {
// get data from query and then setInitialized
setInitialized(true)
}, 500)
}, [])

// makes it convenient to set the defaultValues of forms
if (!initialized) return <ListSkeleton />

return (
<div className="flex flex-col gap-4">
<GeneralFormSection title="Network">
{/* todo pass defualtValues from useQuery */}
<GeneralNetworkForm />
</GeneralFormSection>
<Separator className="mb-8" />
<GeneralFormSection title="Security">
{/* todo pass defualtValues from useQuery */}
<GeneralSecurityForm />
</GeneralFormSection>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use client'

import React from 'react'
import { zodResolver } from '@hookform/resolvers/zod'
import { isEmpty } from 'lodash-es'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { useQuery } from 'urql'
import * as z from 'zod'

import { graphql } from '@/lib/gql/generates'
import { useMutation } from '@/lib/tabby/gql'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'

const updateNetworkSettingMutation = graphql(/* GraphQL */ `
mutation updateNetworkSettingMutation($input: NetworkSettingInput!) {
updateNetworkSetting(input: $input)
}
`)

export const networkSetting = graphql(/* GraphQL */ `
query NetworkSetting {
networkSetting {
externalUrl
}
}
`)

const formSchema = z.object({
externalUrl: z.string()
})

type NetworkFormValues = z.infer<typeof formSchema>

interface NetworkFormProps {
defaultValues?: Partial<NetworkFormValues>
onSuccess?: () => void
}

const NetworkForm: React.FC<NetworkFormProps> = ({
onSuccess,
defaultValues: propsDefaultValues
}) => {
const defaultValues = React.useMemo(() => {
return {
...(propsDefaultValues || {})
}
}, [propsDefaultValues])

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues
})

const isDirty = !isEmpty(form.formState.dirtyFields)

const updateNetworkSetting = useMutation(updateNetworkSettingMutation, {
form,
onCompleted(values) {
if (values?.updateNetworkSetting) {
onSuccess?.()
form.reset(form.getValues())
}
}
})

const onSubmit = async () => {
await updateNetworkSetting({
input: form.getValues()
})
}

return (
<Form {...form}>
<div className="flex flex-col gap-4">
<form
className="flex flex-col gap-8"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control}
name="externalUrl"
render={({ field }) => (
<FormItem>
<FormLabel>External URL</FormLabel>
<FormDescription>
The external URL where user visits Tabby, must start with
http:// or https://.
</FormDescription>
<FormControl>
<Input
placeholder="http://localhost:8080"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mt-2 flex justify-end">
<Button type="submit" disabled={!isDirty}>
Update
</Button>
</div>
</form>
<FormMessage className="text-center" />
</div>
</Form>
)
}

export const GeneralNetworkForm = () => {
const [{ data: data }] = useQuery({ query: networkSetting })
const onSuccess = () => {
toast.success('Network configuration is updated')
}
return (
data && (
<NetworkForm defaultValues={data.networkSetting} onSuccess={onSuccess} />
)
)
}
Loading

0 comments on commit 23661f7

Please sign in to comment.