InstallForm.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import React, { useEffect } from 'react';
  2. import { Controller, useForm } from 'react-hook-form';
  3. import { Button } from '../../../../components/ui/Button';
  4. import { Switch } from '../../../../components/ui/Switch';
  5. import { Input } from '../../../../components/ui/Input';
  6. import { validateAppConfig } from '../../utils/validators';
  7. import { FormField } from '../../../../core/types';
  8. interface IProps {
  9. formFields: FormField[];
  10. onSubmit: (values: FormValues) => void;
  11. initalValues?: { exposed?: boolean; domain?: string } & { [key: string]: string | boolean | undefined };
  12. loading?: boolean;
  13. exposable?: boolean | null;
  14. }
  15. export type FormValues = {
  16. exposed?: boolean;
  17. domain?: string;
  18. [key: string]: string | boolean | undefined;
  19. };
  20. const hiddenTypes = ['random'];
  21. const typeFilter = (field: FormField) => !hiddenTypes.includes(field.type);
  22. export const InstallForm: React.FC<IProps> = ({ formFields, onSubmit, initalValues, exposable, loading }) => {
  23. const {
  24. register,
  25. handleSubmit,
  26. formState: { errors, isDirty },
  27. setValue,
  28. watch,
  29. setError,
  30. control,
  31. } = useForm<FormValues>({});
  32. const watchExposed = watch('exposed', false);
  33. useEffect(() => {
  34. if (initalValues && !isDirty) {
  35. Object.entries(initalValues).forEach(([key, value]) => {
  36. setValue(key, value);
  37. });
  38. }
  39. }, [initalValues, isDirty, setValue]);
  40. const renderField = (field: FormField) => (
  41. <Input
  42. key={field.env_variable}
  43. {...register(field.env_variable)}
  44. label={field.label}
  45. error={errors[field.env_variable]?.message}
  46. disabled={loading}
  47. className="mb-3"
  48. placeholder={field.hint || field.label}
  49. />
  50. );
  51. const renderExposeForm = () => (
  52. <>
  53. <Controller
  54. control={control}
  55. name="exposed"
  56. defaultValue={false}
  57. render={({ field: { onChange, value, ref, ...props } }) => <Switch className="mb-3" ref={ref} checked={value} onCheckedChange={onChange} {...props} label="Expose app" />}
  58. />
  59. {watchExposed && (
  60. <div className="mb-3">
  61. <Input {...register('domain')} label="Domain name" error={errors.domain?.message} disabled={loading} placeholder="Domain name" />
  62. <span className="text-muted">
  63. Make sure this exact domain contains an <strong>A</strong> record pointing to your IP.
  64. </span>
  65. </div>
  66. )}
  67. </>
  68. );
  69. const validate = (values: FormValues) => {
  70. const validationErrors = validateAppConfig(values, formFields);
  71. Object.entries(validationErrors).forEach(([key, value]) => {
  72. if (value) {
  73. setError(key, { message: value });
  74. }
  75. });
  76. if (Object.keys(validationErrors).length === 0) {
  77. onSubmit(values);
  78. }
  79. };
  80. return (
  81. <form className="flex flex-col" onSubmit={handleSubmit(validate)}>
  82. {formFields.filter(typeFilter).map(renderField)}
  83. {exposable && renderExposeForm()}
  84. <Button loading={loading} type="submit" className="btn-success">
  85. {initalValues ? 'Update' : 'Install'}
  86. </Button>
  87. </form>
  88. );
  89. };