formik#useFormik TypeScript Examples

The following examples show how to use formik#useFormik. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useResetPasswordFormik.tsx    From bouncecode-cms with GNU General Public License v3.0 7 votes vote down vote up
function useResetPasswordFormik(
  onSubmit: (
    values: FormikValues,
    formikHelpers: FormikHelpers<FormikValues>,
  ) => void | Promise<any>,
  options?: Partial<FormikConfig<FormikValues>>,
) {
  const {enqueueSnackbar} = useSnackbar();

  const formik = useFormik({
    ...options,
    initialValues: {
      ...initialValues,
      ...options?.initialValues,
    },
    validationSchema,
    onSubmit,
  });

  useEffect(() => {
    if (formik.submitCount > 0 && !formik.isSubmitting && !formik.isValid) {
      enqueueSnackbar('누락된 입력 항목을 확인해주세요.', {
        variant: 'error',
      });
    }
  }, [formik.submitCount, formik.isSubmitting]);

  return formik;
}
Example #2
Source File: RuleForm.tsx    From netify with BSD 2-Clause "Simplified" License 6 votes vote down vote up
RuleForm = memo<RuleFormProps>(function RuleForm({initialRule, onSave, onCancel}) {
	const initialValues = useMemo(() => serializeRuleForm(initialRule), [initialRule]);

	const handleSubmit = useCallback(
		(rawValue: RuleFormSchema) => {
			const value = deserializeRuleForm(rawValue, initialRule.id, initialRule.active);
			onSave(value);
		},
		[initialRule.id, initialRule.active, onSave],
	);

	const form = useFormik<RuleFormSchema>({
		initialValues,
		validateOnBlur: true,
		validateOnChange: false,
		validationSchema: ruleFormSchema,
		onSubmit: handleSubmit,
	});

	return (
		<div className={styles.root}>
			<FormikProvider value={form}>
				<Form className={styles.form}>
					<RuleLabel />
					<RuleFilter />
					<RuleActionSwitcher />
					<div className={styles.config}>
						<RuleActionConfig />
					</div>

					<div className={styles.controls}>
						<Button className={styles.saveButton} styleType='dark' type='submit'>
							Save
						</Button>
						<Button onClick={onCancel}>Cancel</Button>
					</div>
				</Form>
			</FormikProvider>
		</div>
	);
})
Example #3
Source File: useSignInFormik.tsx    From bouncecode-cms with GNU General Public License v3.0 6 votes vote down vote up
function useSigninFormik(
  onSubmit: (
    values: FormikValues,
    formikHelpers: FormikHelpers<FormikValues>,
  ) => void | Promise<any>,
  options?: Partial<FormikConfig<FormikValues>>,
) {
  const {enqueueSnackbar} = useSnackbar();

  const formik = useFormik({
    ...options,
    initialValues: {
      ...initialValues,
      ...options?.initialValues,
    },
    validationSchema,
    onSubmit,
  });

  useEffect(() => {
    if (formik.submitCount > 0 && !formik.isSubmitting && !formik.isValid) {
      enqueueSnackbar('누락된 입력 항목을 확인해주세요.', {
        variant: 'error',
      });
    }
  }, [formik.submitCount, formik.isSubmitting]);

  return formik;
}
Example #4
Source File: useSignUpFormik.tsx    From bouncecode-cms with GNU General Public License v3.0 6 votes vote down vote up
function useSignUpFormik(
  onSubmit: (
    values: FormikValues,
    formikHelpers: FormikHelpers<FormikValues>,
  ) => void | Promise<any>,
  options?: Partial<FormikConfig<FormikValues>>,
) {
  const {enqueueSnackbar} = useSnackbar();

  const formik = useFormik({
    ...options,
    initialValues: {
      ...initialValues,
      ...options?.initialValues,
    },
    validationSchema,
    onSubmit,
  });

  useEffect(() => {
    if (formik.submitCount > 0 && !formik.isSubmitting && !formik.isValid) {
      enqueueSnackbar('누락된 입력 항목을 확인해주세요.', {
        variant: 'error',
      });
    }
  }, [formik.submitCount, formik.isSubmitting]);

  return formik;
}
Example #5
Source File: useForm.ts    From frontend with MIT License 6 votes vote down vote up
export default function useForm<Values>({
  validateOnChange = true,
  validateOnBlur = false,
  ...formikProps
}: FormikConfig<Values>): FormikType<Values> {
  const formik = useFormik<Values>({
    validateOnChange,
    validateOnBlur,
    ...formikProps,
  })

  return { formik }
}
Example #6
Source File: AddAppDomain.tsx    From ledokku with MIT License 5 votes vote down vote up
AddAppDomain = ({ appId, appDomainsRefetch }: AddDomainProps) => {
  const toast = useToast();
  const [addDomainMutation] = useAddDomainMutation();
  const [showAddForm, setShowAddForm] = useState(false);

  const formik = useFormik<{ domainName: string }>({
    initialValues: {
      domainName: '',
    },
    validateOnChange: true,
    validationSchema: addAppDomainYupSchema,
    onSubmit: async (values) => {
      try {
        await addDomainMutation({
          variables: {
            input: {
              appId,
              domainName: values.domainName,
            },
          },
        });

        await appDomainsRefetch();
        toast.success('Domain added successfully');

        formik.resetForm();
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  return (
    <>
      {!showAddForm ? (
        <Button variant="outline" onClick={() => setShowAddForm(true)}>
          Add custom domain
        </Button>
      ) : (
        <form onSubmit={formik.handleSubmit}>
          <FormControl
            id="domainName"
            isInvalid={Boolean(
              formik.errors.domainName && formik.touched.domainName
            )}
          >
            <Input
              placeholder="www.mydomain.com"
              name="domainName"
              value={formik.values.domainName}
              onChange={formik.handleChange}
            />
            <FormErrorMessage>{formik.errors.domainName}</FormErrorMessage>
          </FormControl>

          <ButtonGroup mt="4" spacing="2">
            <Button isLoading={formik.isSubmitting} type="submit">
              Save
            </Button>
            <Button
              variant="outline"
              disabled={formik.isSubmitting}
              onClick={() => setShowAddForm(false)}
            >
              Cancel
            </Button>
          </ButtonGroup>
        </form>
      )}
    </>
  );
}
Example #7
Source File: debug.tsx    From core with GNU Affero General Public License v3.0 5 votes vote down vote up
ClientInfo = ():JSX.Element => {
	const formik = useFormik({
		initialValues: {
			markdown: `<div align="center">
<h1>Hello, World</h1>
</div>
<kbd>X</kbd>키를 눌러 Joy를 표하세요.
\`\`\`
코드 블럭
\`\`\`

*도* **레** ***미*** __***파***__

[트위터](https://twitter.com/koreanbots)
https://github.com/koreanbots
`
		},
		onSubmit: ()=>{ alert('Pong') }
	})
	return <Container paddingTop className='pb-10'>
		<h1 className='text-4xl font-bold mb-3 mt-3'>개발자모드</h1>
		<h2 className='text-3xl font-semibold mb-4'>정보들</h2>
		<Segment>
			<div className='markdown-body text-black dark:text-white'>
				<h1>빌드정보</h1>
				<ul className='list-disc'>
					<li>Tag: <code>{parseDockerhubTag(process.env.NEXT_PUBLIC_TAG)}</code></li>
					<li>Version: <code>v{Package.version}</code></li>
					<li>Hash: <code>{process.env.NEXT_PUBLIC_SOURCE_COMMIT}</code></li>
				</ul>

			</div>
		</Segment>
		<Divider />
		<h2 className='text-3xl font-semibold mb-2'>테스트</h2>
		<h3 className='text-2xl font-semibold mb-2'>마크다운</h3>
		<Segment>
			<div className='lg:flex'>
				<div className='w-full lg:w-1/2 min-h-48'>
					<textarea className='resize-none w-full h-full dark:bg-discord-dark outline-none p-5' name='markdown' value={formik.values.markdown} onChange={formik.handleChange}/>
				</div>
				<div className='w-full lg:w-1/2 p-10'>
					<Markdown text={formik.values.markdown} />
				</div>
			</div>
		</Segment>
	</Container>
}
Example #8
Source File: Promotion.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 5 votes vote down vote up
Promotion: React.FC<PromotionProps> = (props) => {
  const { promotionItems } = props;
  const { t } = useTranslation();
  const { updateCartItems } = useCartData();
  const mcart = localStorage.getItem('mcart') || '';
  const { addError } = useContext(APIErrorContext);

  const initialValues:FormValues = {
    promoCode: '',
  };

  const {handleSubmit, handleChange, values, errors, setErrors} = useFormik({
    initialValues,
    onSubmit: (values) => {
      addPromotion(mcart, values.promoCode)
        .then(() => {
          updateCartItems();
          setErrors({promoCode: ''});
          values.promoCode = ''
        })
        .catch(error => {
          console.error(error);
          addError(error.errors);
        })
    },
  });

  const handleRemove = () => {
    removeCartItem(mcart, promotionItems[0].id)
      .then(() => {
        updateCartItems();
      })
      .catch(error => {
        addError(error.errors);
        console.error(error);
      })
  };

  return (
    <div className="promotion">
      <p className="promotion__txt">{t('gift-card')}</p>
      {promotionItems && promotionItems.length > 0 ? (
        <div className="promotion__wrapper">
          <span className="promotion__code">{promotionItems[0].sku}</span>
          <button className="epbtn --secondary" onClick={handleRemove}>{t('remove')}</button>
        </div>
      ) : (
        <form className="epform" onSubmit={handleSubmit}>
          <div className={`epform__group ${errors.promoCode ? '--error' : ''}`}>
            <input className="epform__input" id="promoCode" type="text" aria-label={t('promo-code')} onChange={handleChange} value={values.promoCode} />
            <div className="epform__error">
              {errors.promoCode ? errors.promoCode : null}
            </div>
          </div>
          <div className="epform__group --btn-container">
            <button className="epbtn --ghost" type="submit" disabled={!values.promoCode}>
              {t('apply')}
            </button>
          </div>
        </form>
      )}
    </div>
  )
}
Example #9
Source File: index.tsx    From tailchat with GNU General Public License v3.0 5 votes vote down vote up
MetaForm: React.FC<MetaFormProps> = React.memo((props) => {
  const initialValues = useMemo(() => {
    return {
      ..._fromPairs(
        props.fields.map((field) => [field.name, field.defaultValue ?? ''])
      ),
      ...props.initialValues,
    };
  }, [props.fields, props.initialValues]);

  const [loading, setLoading] = useState(false);

  useLayoutEffect(() => {
    // 加载时提交一次initialValues
    typeof props.onChange === 'function' && props.onChange(initialValues);
  }, []);

  const formik = useFormik({
    initialValues,
    validationSchema: props.schema,
    onSubmit: async (values) => {
      setLoading(true);
      try {
        _isFunction(props.onSubmit) && (await props.onSubmit(values));
      } finally {
        setLoading(false);
      }
    },
    validate: (values) => {
      _isFunction(props.onChange) && props.onChange(values);
    },
  });
  const { handleSubmit, setFieldValue, values, errors } = formik;

  const MetaFormContainer = getFormContainer();

  if (_isNil(MetaFormContainer)) {
    console.warn('MetaFormContainer 没有被注册');
    return null;
  }

  const fieldsRender = useMemo(() => {
    return props.fields.map((fieldMeta, i) => {
      const fieldName = fieldMeta.name;
      const value = values[fieldName];
      const error = errors[fieldName];
      const Component = getField(fieldMeta.type);

      if (_isNil(Component)) {
        return null;
      } else {
        return (
          <Component
            key={fieldName + i}
            {...fieldMeta}
            value={value}
            error={error}
            onChange={(val: any) => setFieldValue(fieldName, val)}
          />
        );
      }
    });
  }, [props.fields, values, errors, setFieldValue]);

  return (
    <MetaFormContext.Provider value={formik}>
      <MetaFormContainer
        loading={loading}
        layout={props.layout ?? 'horizontal'}
        submitLabel={props.submitLabel}
        handleSubmit={handleSubmit}
        canSubmit={_isEmpty(errors)}
      >
        {fieldsRender}
      </MetaFormContainer>
    </MetaFormContext.Provider>
  );
})
Example #10
Source File: ChangePassword.tsx    From mayoor with MIT License 5 votes vote down vote up
ChangePassword: React.FC = () => {
	const { t } = useTranslation();
	const [changePassword, { loading }] = useMutation<
		ChangePasswordMutation,
		ChangePasswordMutationVariables
	>(CHANGE_PASSWORD_MUTATION);

	const formik = useFormik<FormValues>({
		initialValues: {
			oldPassword: '',
			newPassword: '',
			newPasswordRepeat: '',
		},
		validate: (values) => {
			const errors: FormikErrors<FormValues> = {};
			if (values.newPassword !== values.newPasswordRepeat) {
				errors.newPasswordRepeat = t('new_passwords_must_match');
			}
			return errors;
		},
		onSubmit: async ({ oldPassword, newPassword }) => {
			try {
				await changePassword({ variables: { oldPassword, newPassword } });
				message.success(t('pwd_changed'));
				formik.resetForm();
			} catch (err) {
				if (err instanceof ApolloError) {
					if (err.graphQLErrors[0].extensions?.code === 'INVALID_PASSWORD') {
						formik.setErrors({
							oldPassword: t('old_pwd_incorrect'),
						});
					} else {
						message.error(t('error_changing_pwd'));
					}
				}
			}
		},
	});

	const getPasswordField = (name: keyof FormValues, label: string) => {
		const errorMessage = formik.touched[name] && formik.errors[name];
		const status = errorMessage ? 'error' : '';
		return (
			<FormItemStyled validateStatus={status} help={errorMessage}>
				<Input
					prefix={<LockFilled />}
					placeholder={label}
					name={name}
					onChange={formik.handleChange}
					value={formik.values[name]}
					type="password"
				/>
			</FormItemStyled>
		);
	};

	return (
		<form onSubmit={formik.handleSubmit}>
			<h4>{t('Change your password')}</h4>
			{getPasswordField('oldPassword', t('Old Password'))}
			<Row gutter={16}>
				<Col span={12}>{getPasswordField('newPassword', t('New Password'))}</Col>
				<Col span={12}>
					{getPasswordField('newPasswordRepeat', t('Repeat New Password'))}
				</Col>
			</Row>
			<Button type="primary" htmlType="submit" loading={loading}>
				{t('Change password')}
			</Button>
		</form>
	);
}
Example #11
Source File: useCommentCreateFormik.tsx    From bouncecode-cms with GNU General Public License v3.0 5 votes vote down vote up
export function useCommentCreateFormik(
  postId: string,
  options?: Partial<FormikConfig<FormikValues>>,
) {
  const {enqueueSnackbar} = useSnackbar();
  const [create] = useCommentCreateMutation({
    onCompleted: () => {
      formik.handleReset(null);
    },
  });

  const onSubmit = async (
    values: FormikValues,
    formikHelpers: FormikHelpers<FormikValues>,
  ) => {
    return create({
      variables: {
        data: {
          postId,
          text: values.text,
          payload: {},
        },
      },
      refetchQueries: [CommentsDocument, CommentStatDocument],
    });
  };

  const formik = useFormik({
    ...options,
    initialValues: {
      ...initialValues,
      ...options?.initialValues,
    },
    validationSchema,
    onSubmit,
  });

  useEffect(() => {
    if (formik.submitCount > 0 && !formik.isSubmitting && !formik.isValid) {
      enqueueSnackbar('누락된 입력 항목을 확인해주세요.', {
        variant: 'error',
      });
    }
  }, [formik.submitCount, formik.isSubmitting]);

  return formik;
}
Example #12
Source File: Registration.tsx    From Mokku with MIT License 4 votes vote down vote up
Registration = ({ user, location }: IProps) => {
  const isRegistration = location.pathname === "/auth/register";
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState("");
  const [redirect, setRedirect] = React.useState<string | null>(null);

  React.useEffect(() => {
    if (user) {
      setRedirect("/");
    }
  }, [user]);

  const { values, handleBlur, handleChange, handleSubmit } = useFormik({
    initialValues: {
      name: "",
      email: "",
      password: "",
    },
    onSubmit: (values) => {
      if (isRegistration) {
        signUp(values, setLoading, setError);
      } else {
        signIn(values, setLoading, setError, setRedirect);
      }
    },
  });

  if (redirect) {
    return <Redirect to={redirect} />;
  }

  return (
    <div className="flex flex-column align-items-center justify-content-center h-100">
      {loading && <ProgressBar />}
      <Logo src="../mokku.svg" />
      <h2>Mokku</h2>
      <form className="flex flex-column p-relative">
        <Route path="/auth/register">
          <Input
            placeholder="Name"
            className="mb-8"
            required
            name="name"
            value={values.name}
            onChange={handleChange}
            onBlur={handleBlur}
            disabled={loading}
          />
        </Route>
        <Input
          placeholder="Email"
          className="mb-8"
          required
          name="email"
          type="email"
          value={values.email}
          onChange={handleChange}
          onBlur={handleBlur}
          disabled={loading}
        />
        <Input
          placeholder="Password"
          className="mb-8"
          required
          name="password"
          type="password"
          value={values.password}
          onChange={handleChange}
          onBlur={handleBlur}
          disabled={loading}
        />
        <div className="flex justify-content-between mt-8">
          <div className="mr-8 flex flex-column justify-content-between">
            <StyledLink to="/forget-password">Forget password?</StyledLink>
            {isRegistration && <StyledLink to="/auth">Sign in</StyledLink>}
            {!isRegistration && (
              <StyledLink to="/auth/register">Sign up</StyledLink>
            )}
          </div>
          <Button
            disabled={loading}
            color="white"
            background="primary"
            type="button"
            onClick={() => handleSubmit()}
          >
            {isRegistration ? "SIGN UP" : "SIGN IN"}
          </Button>
        </div>
        {error && (
          <Text className="p-absolute" style={{ bottom: -48 }} color="alert">
            {error}
          </Text>
        )}
      </form>
    </div>
  );
}
Example #13
Source File: LoginForm.tsx    From mayoor with MIT License 4 votes vote down vote up
LoginForm: React.FC = () => {
	const dispatch = useAppDispatch();
	const { t } = useTranslation();
	const [login, { loading }] = useMutation<LoginMutation, LoginMutationVariables>(LOGIN_MUTATION);

	const { errors, handleSubmit, values, handleChange, isValid, setErrors, touched } = useFormik<
		FormValues
	>({
		initialValues: {
			username: '',
			password: '',
		},
		validate: (values) => {
			const errors: FormikErrors<FormValues> = {};
			if (!values.password) {
				errors.password = t('password_required');
			}
			if (!values.username) {
				errors.username = t('username_required');
			}
			return errors;
		},
		onSubmit: async ({ username, password }) => {
			try {
				const result = await login({ variables: { email: username, password } });
				if (result.data?.login) {
					dispatch({
						type: 'SET_CURRENT_USER',
						user: result.data.login.user,
					});
					localStorage.setItem('auth-token', result.data.login.token);
				}
			} catch (err) {
				if (err instanceof ApolloError) {
					if (err.graphQLErrors[0].extensions?.code === 'USER_NOT_FOUND') {
						setErrors({
							username: t('user_not_found'),
						});
					}
					if (err.graphQLErrors[0].extensions?.code === 'INVALID_PASSWORD') {
						setErrors({
							password: t('invalid_password'),
						});
					}
				}
			}
		},
	});

	return (
		<CenteredWrapper>
			<S.LoginWrapper onSubmit={handleSubmit}>
				<S.Logo src={LogoImage} />
				<S.FormItemStyled
					validateStatus={touched.username && errors.username ? 'error' : ''}
					help={touched.username && errors.username}
				>
					<Input
						prefix={<UserOutlined />}
						placeholder={t('Username')}
						name="username"
						onChange={handleChange}
						value={values.username}
						data-test-id="login-username"
					/>
				</S.FormItemStyled>
				<S.FormItemStyled
					validateStatus={touched.password && errors.password ? 'error' : ''}
					help={touched.password && errors.password}
				>
					<Input
						prefix={<LockFilled />}
						placeholder={t('Password')}
						name="password"
						onChange={handleChange}
						value={values.password}
						type="password"
						data-test-id="login-password"
					/>
				</S.FormItemStyled>
				<Button
					icon={<LoginOutlined />}
					loading={loading}
					disabled={!isValid}
					htmlType="submit"
					data-test-id="login-submit-button"
				>
					{t('Log In')}
				</Button>
				<S.LanguageSwitchWrapper>
					<LanguageSwitch />
				</S.LanguageSwitchWrapper>
			</S.LoginWrapper>
		</CenteredWrapper>
	);
}
Example #14
Source File: settings.tsx    From ledokku with MIT License 4 votes vote down vote up
Settings = () => {
  const { id: databaseId } = useParams<{ id: string }>();
  let history = useHistory();
  const toast = useToast();
  const [
    destroyDatabaseMutation,
    { loading: destroyDbLoading },
  ] = useDestroyDatabaseMutation();
  const { data, loading /* error */ } = useDatabaseByIdQuery({
    variables: {
      databaseId,
    },
    ssr: false,
    skip: !databaseId,
  });

  const {
    data: databaseInfoData,
    loading: databaseInfoLoading,
  } = useDatabaseInfoQuery({
    variables: {
      databaseId,
    },
    ssr: false,
    skip: !databaseId,
    pollInterval: 15000,
  });

  const DeleteDatabaseNameSchema = yup.object().shape({
    databaseName: yup
      .string()
      .required('Required')
      .test(
        'Equals database name',
        'Must match database name',
        (val) => val === data?.database?.name
      ),
  });

  const formik = useFormik<{ databaseName: string }>({
    initialValues: {
      databaseName: '',
    },

    validateOnChange: true,
    validationSchema: DeleteDatabaseNameSchema,

    onSubmit: async (values) => {
      try {
        await destroyDatabaseMutation({
          variables: {
            input: { databaseId },
          },
          refetchQueries: [
            {
              query: DashboardDocument,
            },
          ],
        });
        toast.success('Database deleted successfully');

        history.push('/dashboard');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  if (!data) {
    return null;
  }

  // // TODO display error

  if (loading) {
    // TODO nice loading
    return <p>Loading...</p>;
  }

  const { database } = data;

  if (!database) {
    // TODO nice 404
    return <p>Database not found.</p>;
  }

  const databaseInfos = databaseInfoData?.databaseInfo.info
    .map((data) => data.trim())
    .map((info) => {
      const name = info.split(':')[0];
      const value = info.substring(info.indexOf(':') + 1).trim();
      return { name, value };
    });

  return (
    <div>
      <HeaderContainer>
        <Header />
        <DatabaseHeaderInfo database={database} />
        <DatabaseHeaderTabNav database={database} />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <div className="grid sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-1 xl:grid-cols-1 gap-4 mt-10">
          <div className="col-span-2 w-5/5">
            <Heading as="h2" size="md" py={5}>
              Infos
            </Heading>
            {databaseInfoLoading ? (
              <p className="text-gray-400 text-sm">Loading...</p>
            ) : null}
            {!databaseInfoLoading && databaseInfos
              ? databaseInfos.map((info) => (
                  <div key={info.name} className="py-2">
                    <div className="font-semibold">{info.name}</div>
                    <div>{info.value}</div>
                  </div>
                ))
              : null}
          </div>
          <div className="w-3/3 mb-6">
            <h1 className="text-lg font-bold py-5">Database settings</h1>

            <h2 className="text-gray-400 w-6/6">
              This action cannot be undone. This will permanently delete{' '}
              {database.name} database and everything related to it. Please type{' '}
              <b>{database.name}</b> to confirm deletion.
            </h2>
            <form onSubmit={formik.handleSubmit}>
              <div className="grid md:grid-cols-3">
                <div className="mt-4">
                  <FormInput
                    autoComplete="off"
                    id="databaseName"
                    name="databaseName"
                    value={formik.values.databaseName}
                    onChange={formik.handleChange}
                    onBlur={formik.handleBlur}
                    error={Boolean(
                      formik.errors.databaseName && formik.touched.databaseName
                    )}
                  />
                  {formik.errors.databaseName && formik.errors.databaseName ? (
                    <FormHelper status="error">
                      {formik.errors.databaseName}
                    </FormHelper>
                  ) : null}
                  <Button
                    type="submit"
                    isLoading={destroyDbLoading}
                    disabled={
                      !formik.values.databaseName ||
                      !!formik.errors.databaseName
                    }
                    color="red"
                    className="mt-2"
                  >
                    Delete
                  </Button>
                </div>
              </div>
            </form>
          </div>
        </div>
      </Container>
    </div>
  );
}
Example #15
Source File: create-database.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateDatabase = () => {
  const location = useLocation();
  const history = useHistory();
  const toast = useToast();

  const { data: dataDb } = useDatabaseQuery();
  const [arrayOfCreateDbLogs, setArrayofCreateDbLogs] = useState<RealTimeLog[]>(
    []
  );
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [createDatabaseMutation] = useCreateDatabaseMutation();
  const [
    isDbCreationSuccess,
    setIsDbCreationSuccess,
  ] = useState<DbCreationStatus>();

  useCreateDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.createDatabaseLogs;

      if (logsExist) {
        setArrayofCreateDbLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (logsExist.type === 'end:success') {
          setIsDbCreationSuccess(DbCreationStatus.SUCCESS);
        } else if (logsExist.type === 'end:failure') {
          setIsDbCreationSuccess(DbCreationStatus.FAILURE);
        }
      }
    },
  });

  const createDatabaseSchema = yup.object({
    type: yup
      .string()
      .oneOf(['POSTGRESQL', 'MYSQL', 'MONGODB', 'REDIS'])
      .required(),
    name: yup.string().when('type', (type: DatabaseTypes) => {
      return yup
        .string()
        .required('Database name is required')
        .matches(/^[a-z0-9-]+$/)
        .test(
          'Name already exists',
          `You already have created ${type} database with this name`,
          (name) =>
            !dataDb?.databases.find(
              (db) => db.name === name && type === db.type
            )
        );
    }),
  });

  const [
    isDokkuPluginInstalled,
    { data, loading, error: isDokkuPluginInstalledError },
  ] = useIsPluginInstalledLazyQuery({
    // we poll every 5 sec
    pollInterval: 5000,
  });
  const formik = useFormik<{ name: string; type: DatabaseTypes }>({
    initialValues: {
      name: location.state ? (location.state as string) : '',
      type: 'POSTGRESQL',
    },
    validateOnChange: true,
    validationSchema: createDatabaseSchema,
    onSubmit: async (values) => {
      try {
        await createDatabaseMutation({
          variables: {
            input: { name: values.name, type: values.type },
          },
        });
        setIsTerminalVisible(true);

        trackGoal(trackingGoals.createDatabase, 0);
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  const isPluginInstalled = data?.isPluginInstalled.isPluginInstalled;

  const handleNext = () => {
    setIsTerminalVisible(false);
    const dbId = arrayOfCreateDbLogs[arrayOfCreateDbLogs.length - 1].message;
    history.push(`database/${dbId}`);
  };

  // Effect for checking whether plugin is installed
  useEffect(() => {
    isDokkuPluginInstalled({
      variables: {
        pluginName: dbTypeToDokkuPlugin(formik.values.type),
      },
    });
  }, [formik.values.type, isPluginInstalled, isDokkuPluginInstalled]);

  // Effect for db creation
  useEffect(() => {
    isDbCreationSuccess === DbCreationStatus.FAILURE
      ? toast.error('Failed to create database')
      : isDbCreationSuccess === DbCreationStatus.SUCCESS &&
        toast.success('Database created successfully');
  }, [isDbCreationSuccess, toast]);

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" my="4">
        <Heading as="h2" size="md">
          Create a new database
        </Heading>
        <Box mt="12">
          {isTerminalVisible ? (
            <>
              <Text mb="2">
                Creating <b>{formik.values.type}</b> database{' '}
                <b>{formik.values.name}</b>
              </Text>
              <Text mb="2" color="gray.500">
                Creating database usually takes a couple of minutes. Breathe in,
                breathe out, logs are about to appear below:
              </Text>
              <Terminal>
                {arrayOfCreateDbLogs.map((log) => (
                  <Text key={arrayOfCreateDbLogs.indexOf(log)} size="small">
                    {log.message}
                  </Text>
                ))}
              </Terminal>

              {!!isDbCreationSuccess &&
              isDbCreationSuccess === DbCreationStatus.SUCCESS ? (
                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    onClick={() => handleNext()}
                    rightIcon={<FiArrowRight size={20} />}
                  >
                    Next
                  </Button>
                </Box>
              ) : !!isDbCreationSuccess &&
                isDbCreationSuccess === DbCreationStatus.FAILURE ? (
                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    onClick={() => {
                      setIsTerminalVisible(false);
                      formik.resetForm();
                    }}
                    rightIcon={<FiArrowLeft size={20} />}
                  >
                    Back
                  </Button>
                </Box>
              ) : null}
            </>
          ) : (
            <Box mt="8">
              <form onSubmit={formik.handleSubmit}>
                <Box mt="12">
                  {loading && (
                    <Center>
                      <Spinner />
                    </Center>
                  )}
                  {isDokkuPluginInstalledError ? (
                    <Alert
                      status="error"
                      variant="top-accent"
                      flexDirection="column"
                      alignItems="flex-start"
                      borderBottomRadius="base"
                      boxShadow="md"
                    >
                      <AlertTitle mr={2}>Request failed</AlertTitle>
                      <AlertDescription>
                        {isDokkuPluginInstalledError.message}
                      </AlertDescription>
                    </Alert>
                  ) : null}
                  {data?.isPluginInstalled.isPluginInstalled === false &&
                    !loading && (
                      <>
                        <Text mt="3">
                          Before creating a{' '}
                          <b>{formik.values.type.toLowerCase()}</b> database,
                          you will need to run this command on your dokku
                          server.
                        </Text>
                        <Terminal>{`sudo dokku plugin:install https://github.com/dokku/dokku-${dbTypeToDokkuPlugin(
                          formik.values.type
                        )}.git ${dbTypeToDokkuPlugin(
                          formik.values.type
                        )}`}</Terminal>
                        <Text mt="3">
                          Couple of seconds later you will be able to proceed
                          further.
                        </Text>
                      </>
                    )}
                  {data?.isPluginInstalled.isPluginInstalled === true &&
                    !loading && (
                      <SimpleGrid columns={{ sm: 1, md: 3 }}>
                        <FormControl
                          id="name"
                          isInvalid={Boolean(
                            formik.errors.name && formik.touched.name
                          )}
                        >
                          <FormLabel>Database name</FormLabel>
                          <Input
                            autoComplete="off"
                            id="name"
                            name="name"
                            value={formik.values.name}
                            onChange={formik.handleChange}
                            onBlur={formik.handleBlur}
                          />
                          <FormErrorMessage>
                            {formik.errors.name}
                          </FormErrorMessage>
                        </FormControl>
                      </SimpleGrid>
                    )}
                </Box>

                <Box mt="12">
                  <Text mb="2">Choose your database</Text>
                  <Grid
                    templateColumns={{
                      base: 'repeat(2, minmax(0, 1fr))',
                      md: 'repeat(4, minmax(0, 1fr))',
                    }}
                    gap="4"
                  >
                    <DatabaseBox
                      selected={formik.values.type === 'POSTGRESQL'}
                      label="PostgreSQL"
                      icon={<PostgreSQLIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'POSTGRESQL')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'MYSQL'}
                      label="MySQL"
                      icon={<MySQLIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'MYSQL')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'MONGODB'}
                      label="Mongo"
                      icon={<MongoIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'MONGODB')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'REDIS'}
                      label="Redis"
                      icon={<RedisIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'REDIS')}
                    />
                  </Grid>
                </Box>

                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    isLoading={formik.isSubmitting}
                    disabled={
                      data?.isPluginInstalled.isPluginInstalled === false ||
                      !formik.values.name ||
                      !!formik.errors.name ||
                      !dataDb?.databases
                    }
                    rightIcon={<FiArrowRight size={20} />}
                    type="submit"
                  >
                    Create
                  </Button>
                </Box>
              </form>
            </Box>
          )}
        </Box>
      </Container>
    </>
  );
}
Example #16
Source File: create-app.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateApp = () => {
  const history = useHistory();
  const toast = useToast();

  const createAppSchema = yup.object({
    type: yup
      .string()
      .oneOf(['GITHUB', 'GITLAB', 'DOCKER', 'DOKKU'])
      .required(),
  });

  const formik = useFormik<{ type: AppTypes }>({
    initialValues: {
      type: AppTypes.GITHUB,
    },
    validateOnChange: true,
    validationSchema: createAppSchema,
    onSubmit: async (values) => {
      try {
        values.type === AppTypes.GITHUB
          ? history.push('/create-app-github')
          : history.push('/create-app-dokku');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" my="4">
        <Heading py="2" as="h2" size="md">
          App source
        </Heading>
        <Box mt="24">
          <Box mt="4">
            <form onSubmit={formik.handleSubmit}>
              <Box mt="20">
                <Text mb="5" color="gray.500">
                  Choose between creating app from a Github repository or
                  creating a standalone Dokku app.
                </Text>
                <Grid
                  templateColumns={{
                    base: 'repeat(2, minmax(0, 1fr))',
                    md: 'repeat(4, minmax(0, 1fr))',
                  }}
                  gap="4"
                >
                  <SourceBox
                    selected={formik.values.type === AppTypes.GITHUB}
                    label="GitHub"
                    icon={<GithubIcon size={40} />}
                    onClick={() => formik.setFieldValue('type', 'GITHUB')}
                  />
                  <SourceBox
                    selected={formik.values.type === AppTypes.DOKKU}
                    label="Dokku"
                    icon={
                      <Image
                        boxSize="48px"
                        objectFit="cover"
                        src="/dokku.png"
                        alt="dokkuLogo"
                      />
                    }
                    onClick={() => formik.setFieldValue('type', 'DOKKU')}
                  />
                  <SourceBox
                    selected={formik.values.type === AppTypes.GITLAB}
                    label="Gitlab"
                    icon={<GitlabIcon size={40} />}
                    badge={
                      <Badge ml="1" colorScheme="red">
                        Coming soon
                      </Badge>
                    }
                    // Uncomment this when we can handle docker deployments
                    // onClick={() => formik.setFieldValue('type', 'GITLAB')}
                  />
                  <SourceBox
                    selected={formik.values.type === AppTypes.DOCKER}
                    label="Docker"
                    icon={<DockerIcon size={40} />}
                    badge={
                      <Badge ml="1" colorScheme="red">
                        Coming soon
                      </Badge>
                    }
                    // Uncomment this when we can handle docker deployments
                    // onClick={() => formik.setFieldValue('type', 'DOCKER')}
                  />
                </Grid>
              </Box>

              <Box mt="36" display="flex" justifyContent="flex-end">
                <Button
                  isLoading={formik.isSubmitting}
                  disabled={!formik.values.type || !!formik.errors.type}
                  rightIcon={<FiArrowRight size={20} />}
                  type="submit"
                >
                  Next
                </Button>
              </Box>
            </form>
          </Box>
        </Box>
      </Container>
    </>
  );
}
Example #17
Source File: create-app-github.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateAppGithub = () => {
  const history = useHistory();
  const toast = useToast();
  const { user } = useAuth();

  const { data: dataApps } = useAppsQuery();
  const [isNewWindowClosed, setIsNewWindowClosed] = useState(false);
  const [selectedRepo, setSelectedRepo] = useState<Repository>();
  const [selectedBranch, setSelectedBranch] = useState('');
  const [isProceedModalOpen, setIsProceedModalOpen] = useState(false);
  const {
    data: installationData,
    loading: installationLoading,
  } = useGithubInstallationIdQuery({ fetchPolicy: 'network-only' });
  const [
    getRepos,
    { data: reposData, loading: reposLoading },
  ] = useRepositoriesLazyQuery({ fetchPolicy: 'network-only' });

  const [
    getBranches,
    { data: branchesData, loading: branchesLoading },
  ] = useBranchesLazyQuery({ fetchPolicy: 'network-only' });

  const [arrayOfCreateAppLogs, setArrayOfCreateAppLogs] = useState<
    RealTimeLog[]
  >([]);
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [isToastShown, setIsToastShown] = useState(false);
  const [createAppGithubMutation, { loading }] = useCreateAppGithubMutation();
  const [
    isAppCreationSuccess,
    setIsAppCreationSuccess,
  ] = useState<AppCreationStatus>();

  useAppCreateLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.appCreateLogs;

      if (logsExist) {
        setArrayOfCreateAppLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (logsExist.type === 'end:success') {
          setIsAppCreationSuccess(AppCreationStatus.SUCCESS);
        } else if (logsExist.type === 'end:failure') {
          setIsAppCreationSuccess(AppCreationStatus.FAILURE);
        }
      }
    },
  });

  const createAppGithubSchema = yup.object().shape({
    name: yup
      .string()
      .required('App name is required')
      .matches(/^[a-z0-9-]+$/)
      .test(
        'Name exists',
        'App with this name already exists',
        (val) => !dataApps?.apps.find((app) => app.name === val)
      ),
    repo: yup.object({
      fullName: yup.string().required(),
      id: yup.string().required(),
      name: yup.string().required(),
    }),
    installationId: yup.string().required(),
    gitBranch: yup.string().optional(),
  });

  const formik = useFormik<{
    name: string;
    repo: {
      fullName: string;
      id: string;
      name: string;
    };
    installationId: string;
    gitBranch: string;
  }>({
    initialValues: {
      name: '',
      repo: {
        fullName: '',
        id: '',
        name: '',
      },
      installationId: '',
      gitBranch: '',
    },

    validateOnChange: true,
    validationSchema: createAppGithubSchema,
    onSubmit: async (values) => {
      if (installationData) {
        try {
          await createAppGithubMutation({
            variables: {
              input: {
                name: values.name,
                gitRepoFullName: values.repo.fullName,
                branchName: values.gitBranch,
                gitRepoId: values.repo.id,
                githubInstallationId: values.installationId,
              },
            },
          });
          setIsTerminalVisible(true);
        } catch (error) {
          error.message === 'Not Found'
            ? toast.error(`Repository : ${values.repo.fullName} not found`)
            : toast.error(error.message);
        }
      }
    },
  });

  const handleNext = () => {
    setIsTerminalVisible(false);
    const appId = arrayOfCreateAppLogs[arrayOfCreateAppLogs.length - 1].message;
    history.push(`app/${appId}`, 'new');
    trackGoal(trackingGoals.createAppGithub, 0);
  };

  const handleOpen = () => {
    const newWindow = window.open(
      `https://github.com/apps/${config.githubAppName}/installations/new`,
      'Install App',
      'resizable=1, scrollbars=1, fullscreen=0, height=1000, width=1020,top=' +
        window.screen.width +
        ', left=' +
        window.screen.width +
        ', toolbar=0, menubar=0, status=0'
    );
    const timer = setInterval(async () => {
      if (newWindow && newWindow.closed) {
        setIsNewWindowClosed(true);
        clearInterval(timer);
      }
    }, 100);
  };

  useEffect(() => {
    if (!installationLoading && installationData && isNewWindowClosed) {
      getRepos({
        variables: {
          installationId: installationData.githubInstallationId.id,
        },
      });

      setIsNewWindowClosed(false);
    }
  }, [
    installationData,
    installationLoading,
    isNewWindowClosed,
    setIsNewWindowClosed,
    getRepos,
  ]);

  useEffect(() => {
    if (
      !installationLoading &&
      installationData &&
      !reposLoading &&
      reposData &&
      selectedRepo
    ) {
      getBranches({
        variables: {
          installationId: installationData.githubInstallationId.id,
          repositoryName: selectedRepo.name,
        },
      });
    }
  }, [
    installationData,
    installationLoading,
    reposData,
    reposLoading,
    getBranches,
    selectedRepo?.name,
    selectedRepo,
  ]);

  const handleChangeRepo = (active: RepoOption) => {
    setSelectedRepo(active.value);
    setSelectedBranch('');
    if (installationData) {
      formik.setValues({
        name: active.value.name,
        installationId: installationData?.githubInstallationId.id,
        repo: {
          fullName: active.value.fullName,
          name: active.value.name,
          id: active.value.id,
        },
        gitBranch: '',
      });
    }
  };

  const handleChangeBranch = (active: BranchOption) => {
    setSelectedBranch(active.value.name);
    formik.setFieldValue('gitBranch', active.value.name);
  };

  const repoOptions: RepoOption[] = [];

  if (reposData && !reposLoading) {
    reposData?.repositories.map((r) =>
      repoOptions.push({ value: r, label: r.fullName })
    );
  }

  let branchOptions: BranchOption[] = [];

  if (branchesData && !branchesLoading) {
    branchesData.branches.map((b) =>
      branchOptions.push({ value: b, label: b.name })
    );
  }

  useEffect(() => {
    if (installationData && !installationLoading) {
      getRepos({
        variables: {
          installationId: installationData?.githubInstallationId.id,
        },
      });
    }
  }, [installationLoading, getRepos, installationData]);

  useEffect(() => {
    if (selectedRepo && installationData) {
      getBranches({
        variables: {
          installationId: installationData?.githubInstallationId.id,
          repositoryName: selectedRepo.name,
        },
      });
    }
  }, [selectedRepo, getBranches, installationData]);

  // Effect for app creation
  useEffect(() => {
    isAppCreationSuccess === AppCreationStatus.FAILURE && !isToastShown
      ? toast.error('Failed to create an app') && setIsToastShown(true)
      : isAppCreationSuccess === AppCreationStatus.SUCCESS &&
        !isToastShown &&
        toast.success('App created successfully') &&
        setIsToastShown(true);
  }, [isToastShown, isAppCreationSuccess, toast]);

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        {isTerminalVisible ? (
          <>
            <p className="mb-2 ">
              Creating <b>{formik.values.name}</b> app from{' '}
              <b>{formik.values.repo.name}</b>
            </p>
            <p className="text-gray-500 mb-2">
              Creating app usually takes a couple of minutes. Breathe in,
              breathe out, logs are about to appear below:
            </p>
            <Terminal className={'w-6/6'}>
              {arrayOfCreateAppLogs.map((log) => (
                <p
                  key={arrayOfCreateAppLogs.indexOf(log)}
                  className={'text-s leading-5'}
                >
                  {log.message?.replaceAll('[1G', '')}
                </p>
              ))}
            </Terminal>

            {!!isAppCreationSuccess &&
            isAppCreationSuccess === AppCreationStatus.SUCCESS ? (
              <div className="mt-12 flex justify-end">
                <Button
                  onClick={() => handleNext()}
                  color="grey"
                  iconEnd={<FiArrowRight size={20} />}
                >
                  Next
                </Button>
              </div>
            ) : !!isAppCreationSuccess &&
              isAppCreationSuccess === AppCreationStatus.FAILURE ? (
              <div className="mt-12 flex justify-start">
                <Button
                  onClick={() => {
                    setIsTerminalVisible(false);
                    formik.resetForm();
                  }}
                  color="grey"
                  iconEnd={<FiArrowLeft size={20} />}
                >
                  Back
                </Button>
              </div>
            ) : null}
          </>
        ) : (
          <>
            <Heading as="h2" size="md">
              Create a new GitHub application
            </Heading>
            {installationData &&
            !installationLoading &&
            reposData &&
            !reposLoading ? (
              <>
                <Text color="gray.400">
                  When you push to Git, your application will be redeployed
                  automatically.
                </Text>

                <Grid
                  templateColumns={{
                    sm: 'repeat(1, 1fr)',
                    md: 'repeat(3, 1fr)',
                  }}
                >
                  <GridItem colSpan={2}>
                    <Flex alignItems="center" mt="12">
                      <Avatar
                        size="sm"
                        name={user?.userName}
                        src={user?.avatarUrl}
                      />
                      <Text ml="2" fontWeight="bold">
                        {user?.userName}
                      </Text>
                    </Flex>
                    <form onSubmit={formik.handleSubmit}>
                      <Box mt="8">
                        <FormLabel>Repository</FormLabel>
                        <Select
                          placeholder="Select repository"
                          isSearchable={false}
                          onChange={handleChangeRepo}
                          options={repoOptions}
                        />
                      </Box>

                      <Text mt="1" color="gray.400" fontSize="sm">
                        Can't see your repo in the list?{' '}
                        <Link
                          onClick={() => setIsProceedModalOpen(true)}
                          textDecoration="underline"
                        >
                          Configure the GitHub app.
                        </Link>
                      </Text>

                      <Box mt="8">
                        <FormLabel>Branch to deploy</FormLabel>
                        <Select
                          placeholder="Select branch"
                          isSearchable={false}
                          disabled={
                            !branchesData ||
                            branchesLoading ||
                            reposLoading ||
                            !reposData
                          }
                          onChange={handleChangeBranch}
                          options={branchOptions}
                        />
                      </Box>

                      <Box mt="8" display="flex" justifyContent="flex-end">
                        <Button
                          type="submit"
                          color="grey"
                          disabled={!selectedBranch || !selectedRepo}
                          isLoading={loading}
                        >
                          Create
                        </Button>
                      </Box>
                    </form>
                  </GridItem>
                </Grid>
              </>
            ) : !reposLoading && !installationLoading && !reposData ? (
              <>
                <Alert mb="4" mt="4" w="65%" status="info">
                  <AlertIcon />
                  <Box flex="1">
                    <AlertTitle>Set up repository permissions</AlertTitle>
                    <AlertDescription display="block">
                      First you will need to set up permissions for repositories
                      that you would like to use with Ledokku. Once that's done,
                      it's time to choose repo and branch that you would like to
                      create app from and off we go.
                    </AlertDescription>
                  </Box>
                </Alert>

                <Button
                  color="grey"
                  onClick={() => setIsProceedModalOpen(true)}
                >
                  Set up permissions
                </Button>
              </>
            ) : (
              <Spinner />
            )}
          </>
        )}
        <Modal
          isOpen={isProceedModalOpen}
          onClose={() => setIsProceedModalOpen(false)}
          isCentered
        >
          <ModalOverlay />
          <ModalContent>
            <ModalHeader>Github setup info</ModalHeader>
            <ModalCloseButton />
            <ModalBody>
              New window is about to open. After you are done selecting github
              repos, close the window and refresh page.
            </ModalBody>

            <ModalFooter>
              <Button
                color="grey"
                variant="outline"
                className="mr-3"
                onClick={() => setIsProceedModalOpen(false)}
              >
                Cancel
              </Button>
              <Button
                color="grey"
                onClick={() => {
                  handleOpen();
                  setIsProceedModalOpen(false);
                }}
              >
                Proceed
              </Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      </Container>
    </>
  );
}
Example #18
Source File: create-app-dokku.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateAppDokku = () => {
  const history = useHistory();
  const toast = useToast();
  const { data: dataApps } = useAppsQuery();
  const [createAppDokkuMutation, { loading }] = useCreateAppDokkuMutation();

  const createAppSchema = yup.object().shape({
    name: yup
      .string()
      .required('App name is required')
      .matches(/^[a-z0-9-]+$/)
      .test(
        'Name exists',
        'App with this name already exists',
        (val) => !dataApps?.apps.find((app) => app.name === val)
      ),
  });

  const formik = useFormik<{
    name: string;
  }>({
    initialValues: {
      name: '',
    },

    validateOnChange: true,
    validationSchema: createAppSchema,
    onSubmit: async (values) => {
      try {
        const res = await createAppDokkuMutation({
          variables: {
            input: {
              name: values.name,
            },
          },
        });

        trackGoal(trackingGoals.createAppDokku, 0);

        if (res.data) {
          history.push(`app/${res.data?.createAppDokku.appId}`);
          toast.success('App created successfully');
        }
      } catch (error) {
        toast.error(error);
      }
    },
  });

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <Heading as="h2" size="md">
          Create a new app
        </Heading>

        <Text mt="12" mb="4" color="gray.400">
          Enter app name, click create and voila!
        </Text>

        <Grid templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}>
          <GridItem colSpan={2}>
            <form onSubmit={formik.handleSubmit}>
              <FormControl
                id="v"
                isInvalid={Boolean(formik.errors.name && formik.touched.name)}
              >
                <FormLabel>App name:</FormLabel>
                <Input
                  autoComplete="off"
                  id="name"
                  name="name"
                  placeholder="Name"
                  value={formik.values.name}
                  onChange={formik.handleChange}
                  onBlur={formik.handleBlur}
                />
                <FormErrorMessage>{formik.errors.name}</FormErrorMessage>
              </FormControl>

              <Box mt="4" display="flex" justifyContent="flex-end">
                <Button
                  type="submit"
                  color="grey"
                  disabled={
                    loading || !formik.values.name || !!formik.errors.name
                  }
                  isLoading={loading}
                >
                  Create
                </Button>
              </Box>
            </form>
          </GridItem>
        </Grid>
      </Container>
    </>
  );
}
Example #19
Source File: advanced.tsx    From ledokku with MIT License 4 votes vote down vote up
AppSettingsAdvanced = () => {
  const { id: appId } = useParams<{ id: string }>();
  const toast = useToast();
  const history = useHistory();

  const { data, loading } = useAppByIdQuery({
    variables: {
      appId,
    },
  });

  const [
    destroyAppMutation,
    { loading: destroyAppMutationLoading },
  ] = useDestroyAppMutation();

  const DeleteAppNameSchema = yup.object().shape({
    appName: yup
      .string()
      .required('Required')
      .test(
        'Equals app name',
        'Must match app name',
        (val) => val === data?.app?.name
      ),
  });

  const formik = useFormik<{ appName: string }>({
    initialValues: {
      appName: '',
    },

    validateOnChange: true,
    validationSchema: DeleteAppNameSchema,

    onSubmit: async (values) => {
      try {
        await destroyAppMutation({
          variables: {
            input: { appId },
          },
          refetchQueries: [
            {
              query: DashboardDocument,
            },
          ],
        });
        toast.success('App deleted successfully');

        history.push('/dashboard');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  // TODO display error

  if (loading) {
    // TODO nice loading
    return <p>Loading...</p>;
  }

  if (!data?.app) {
    // TODO nice 404
    return <p>App not found.</p>;
  }

  const { app } = data;

  return (
    <>
      <HeaderContainer>
        <Header />
        <AppHeaderInfo app={app} />
        <AppHeaderTabNav app={app} />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <Grid
          templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(6, 1fr)' }}
          gap={{ sm: 0, md: 16 }}
        >
          <GridItem colSpan={2} py={5}>
            <AppSettingsMenu app={app} />
          </GridItem>
          <GridItem colSpan={4}>
            <AppRestart appId={app.id} />
            <AppRebuild appId={app.id} />

            <Box py="5">
              <Heading as="h2" size="md">
                Delete app
              </Heading>
              <Text fontSize="sm" color="gray.400">
                This action cannot be undone. This will permanently delete{' '}
                {app.name} app and everything related to it. Please type{' '}
                <b>{app.name}</b> to confirm deletion.
              </Text>
            </Box>

            <form onSubmit={formik.handleSubmit}>
              <FormControl
                id="appName"
                isInvalid={Boolean(
                  formik.errors.appName && formik.touched.appName
                )}
              >
                <Input
                  autoComplete="off"
                  id="appNme"
                  name="appName"
                  placeholder="App name"
                  value={formik.values.appName}
                  onChange={formik.handleChange}
                  onBlur={formik.handleBlur}
                />
                <FormErrorMessage>{formik.errors.appName}</FormErrorMessage>
              </FormControl>

              <Button
                my={4}
                type="submit"
                colorScheme="red"
                isLoading={destroyAppMutationLoading}
              >
                Delete
              </Button>
            </form>
          </GridItem>
        </Grid>
      </Container>
    </>
  );
}
Example #20
Source File: env.tsx    From ledokku with MIT License 4 votes vote down vote up
EnvForm = ({ name, value, appId, isNewVar }: EnvFormProps) => {
  const [inputType, setInputType] = useState('password');
  const toast = useToast();
  const [
    setEnvVarMutation,
    { loading: setEnvVarLoading },
  ] = useSetEnvVarMutation();
  const [
    unsetEnvVarMutation,
    { loading: unsetEnvVarLoading },
  ] = useUnsetEnvVarMutation();

  const handleDeleteEnvVar = async (event: any) => {
    event.preventDefault();
    try {
      await unsetEnvVarMutation({
        variables: { key: name, appId },
        refetchQueries: [{ query: EnvVarsDocument, variables: { appId } }],
      });
    } catch (error) {
      toast.error(error.message);
    }
  };

  const formik = useFormik<{ name: string; value: string }>({
    initialValues: {
      name,
      value,
    },
    onSubmit: async (values) => {
      // TODO validate values
      try {
        await setEnvVarMutation({
          variables: { key: values.name, value: values.value, appId },
          refetchQueries: [{ query: EnvVarsDocument, variables: { appId } }],
        });

        if (isNewVar) {
          formik.resetForm();
        }
        toast.success('Environment variable set successfully');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  return (
    //TODO Handle visual feedback on changing env
    //TODO Provide infos about env vars
    <form onSubmit={formik.handleSubmit} autoComplete="off">
      <Grid
        templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}
        gap="3"
        mt="3"
      >
        <GridItem>
          <Input
            autoComplete="off"
            id={isNewVar ? 'newVarName' : name}
            name="name"
            placeholder="Name"
            key={name}
            value={formik.values.name}
            onChange={formik.handleChange}
          />
        </GridItem>
        <GridItem>
          <Input
            autoComplete="off"
            onMouseEnter={() => setInputType('text')}
            onMouseLeave={() => setInputType('password')}
            onFocus={() => setInputType('text')}
            onBlur={() => setInputType('password')}
            id={isNewVar ? 'newVarValue' : value}
            name="value"
            placeholder="Value"
            key={value}
            value={formik.values.value}
            onChange={formik.handleChange}
            type={inputType}
          />
        </GridItem>
        <GridItem display="flex">
          <Button isLoading={setEnvVarLoading} type="submit">
            {isNewVar ? 'Add' : 'Save'}
          </Button>
          {!isNewVar && (
            <IconButton
              aria-label="Delete"
              variant="outline"
              ml="3"
              icon={<FiTrash2 />}
              isLoading={unsetEnvVarLoading}
              onClick={handleDeleteEnvVar}
            />
          )}
        </GridItem>
      </Grid>
    </form>
  );
}
Example #21
Source File: SettingsCart.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
SettingsCart: React.FC<SettingsCartParams> = (props) => {
  const { name, description, isEditCart, title, onCartCreate, showSettings, handleHideSettings, setShowCartAlert } = props;
  const { t } = useTranslation();
  const { createCart, editCart } = useMultiCartData();
  const [isLoading, setIsLoading] = useState(false);
  const [nameError, setNameError ] = useState("");
  const [descriptionError , setDescriptionError ] = useState("");


  let initialValues: FormValues = {
    name: isEditCart && name ? name.toString() : '',
    description: isEditCart && description ? description.toString() : '',
  };

  const validate = (values:any) => {
    const errors:any = {};
    if (!values.name) {
      errors.name = t('cart-name-is-required');
    }
    return errors;
  };

  const {handleSubmit, handleChange, values, errors, setFieldValue, resetForm} = useFormik({
    initialValues,
    validate,
    onSubmit: async (values)  => {
      setIsLoading(true);
      try{
        if(isEditCart) {
          await editCart(values);
        }
        else {
          const cartData = await createCart(values);
          if (onCartCreate) onCartCreate(cartData);
        }
        setIsLoading(false);
        handleHideSettings();
        setShowCartAlert();
        resetForm();
        if(descriptionError !== "" || nameError !== ""){
          setDescriptionError("");
          setNameError("");
        }
      }
      catch(errors) {
        errors.errors.map((error:any) => {
          if(error.source === "data.name") {
            setNameError(error.detail)
          }
          else if (error.source === "data.description"){
            setDescriptionError(error.detail)
          }
          return setIsLoading(false);
        })
      }
    },
  });

  return (
    <div className={`settingscart${showSettings ? ' --show' : ''}`}>
      <div className="settingscart__addcartform">
        {isEditCart &&
          <button className="settingscart__closebutton" type="button" aria-label="close" onClick={handleHideSettings}>
            <BackArrowIcon/>
          </button>
        }
        {title ?? (
          <h2 className="settingscart__title">
            {isEditCart ? `${t("cart")} ${t("settings")}` : t("new-cart")}
          </h2>
        )}
        <form>
          <div className={`epform__group ${errors.name ? '--error' : ''}`}>
            <label className="epform__label" htmlFor="name">{t('cart-name')}</label>
            <input className="epform__input" id="name" placeholder={t('new-cart')} onChange={handleChange} value={values.name} />
            {(values.name && values.name.length > 0) && (
              <button tabIndex={-1} type="button" className="settingscart__clearname settingscart__clearbtn" onClick={() => setFieldValue('name', '')}>
                <ClearIcon />
              </button>
            )}
            <div className="epform__error">
              {errors.name ? errors.name : null}
              {values.name.length > 250 ? nameError : null}
            </div>
          </div>
          <div className="epform__group">
            <label className="epform__label" htmlFor="description">{t('cart-description')}</label>
            <textarea className="epform__input" id="description" onChange={handleChange} value={values.description} placeholder={t('carts-description')} />
            {(values.description && values.description.length > 0) && (
              <button tabIndex={-1} className="settingscart__clearbtn" onClick={() => setFieldValue('description', '')}>
                <ClearIcon />
              </button>
            )}
            <div className="epform__error">
              {values.description.length > 250 ? descriptionError : null}
            </div>
          </div>
          <div className="settingscart__btns">
            <button className="epbtn --bordered" type="button" onClick={handleHideSettings}>{t('cancel')}</button>
            <button
              className={`epbtn --primary ${
                isLoading ? "--loading" : ""
                }`}
              type="submit"
              onClick={() => {handleSubmit()}}
              disabled={isLoading || !values.name }
            >
              {!isLoading? t("save") : <span className="circularLoader" aria-label={t('loading')} />}
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}
Example #22
Source File: AddAppProxyPorts.tsx    From ledokku with MIT License 4 votes vote down vote up
AddAppProxyPorts = ({
  appId,
  appProxyPortsRefetch,
  open,
  onClose,
}: AddAppProxyPortsProps) => {
  const [addAppProxyPortMutation] = useAddAppProxyPortMutation();
  const toast = useToast();
  const formik = useFormik<{ host: string; container: string }>({
    initialValues: {
      host: '',
      container: '',
    },
    validateOnChange: true,
    validationSchema: createAppProxyPortSchema,
    onSubmit: async (values) => {
      try {
        await addAppProxyPortMutation({
          variables: {
            input: {
              appId,
              host: values.host,
              container: values.container,
            },
          },
        });
        await appProxyPortsRefetch();
        toast.success('Port mapping created successfully');

        onClose();
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  if (!open) {
    return null;
  }

  return (
    <Modal isOpen={open} onClose={onClose} isCentered size="xl">
      <ModalOverlay />
      <ModalContent>
        <ModalHeader>Add port mapping</ModalHeader>
        <ModalCloseButton />
        <ModalBody>
          <SimpleGrid columns={{ sm: 1, md: 2 }} spacing={3}>
            <FormControl
              id="host"
              isInvalid={Boolean(formik.errors.host && formik.touched.host)}
            >
              <FormLabel>Host port:</FormLabel>
              <Input
                name="host"
                value={formik.values.host}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
              />
              <FormErrorMessage>{formik.errors.host}</FormErrorMessage>
            </FormControl>
            <FormControl
              id="container"
              isInvalid={Boolean(
                formik.errors.container && formik.touched.container
              )}
            >
              <FormLabel>Container port:</FormLabel>
              <Input
                name="container"
                value={formik.values.container}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
              />
              <FormErrorMessage>{formik.errors.container}</FormErrorMessage>
            </FormControl>
          </SimpleGrid>
        </ModalBody>

        <ModalFooter>
          <Button mr={3} onClick={onClose}>
            Cancel
          </Button>
          <Button
            colorScheme="red"
            isLoading={formik.isSubmitting}
            onClick={() => formik.handleSubmit()}
          >
            Create
          </Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
}
Example #23
Source File: index.tsx    From GroupChat with MIT License 4 votes vote down vote up
Login: React.FC<Props> = props => {
  const dispatch = useDispatch();
  const history = useHistory();

  const [isLoading, setIsLoading] = useState(false);
  const [checked, setChecked] = useState(false);
  const [snack, setSnack] = useState<SnackData>({ open: false, message: null });

  // Async Requests
  const loginSubmit = async (checked: boolean, email: string, password: string) => {
    setIsLoading(true);
    let response;
    try {
      response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/login`, {
        checked,
        email: email.toLowerCase(),
        password: password.toLowerCase()
      });
    } catch (error) {
      console.log('[ERROR][AUTH][LOGIN]: ', error);
      setIsLoading(false);
      return;
    }
    if (!response.data.access) {
      setSnack({ open: true, message: response.data.message });
      setIsLoading(false);
      return;
    }
    if (checked) {
      localStorage.setItem('userData', JSON.stringify({ id: response.data.user.id, token: response.data.user.token }));
    }
    dispatch({ type: 'LOGIN', payload: { ...response.data.user } });
    history.push('');
    setIsLoading(false);
  };

  const formik = useFormik({
    initialValues: {
      email: '',
      password: ''
    },
    validationSchema: Yup.object({
      email: Yup.string().email('Invalid email address').required('Required'),
      password: Yup.string()
        .min(6, 'Must be 6 characters at least')
        .required('Required')
        .max(20, 'Can not exceed 20 characters')
    }),
    onSubmit: values => loginSubmit(checked, values.email, values.password)
  });

  return (
    <div className={styles.container}>
      <Link to="/">
        <img className={styles.logo} alt="logo" src={logo} />
      </Link>
      <form className={styles.form}>
        <TextField
          className={styles.input}
          id="email"
          label="Email"
          variant="outlined"
          type="text"
          helperText={formik.touched.email && formik.errors.email}
          error={formik.touched.email && !!formik.errors.email}
          {...formik.getFieldProps('email')}
        />
        <TextField
          className={styles.input}
          id="password"
          label="Password"
          variant="outlined"
          type="password"
          {...formik.getFieldProps('password')}
          helperText={formik.touched.password && formik.errors.password}
          error={formik.touched.password && !!formik.errors.password}
        />
        <FormControlLabel
          className={styles.check}
          control={
            <Checkbox checked={checked} onChange={() => setChecked(prev => !prev)} name="checked" color="primary" />
          }
          label="Remember me"
        />
        <CustomButton type="submit" onClick={formik.handleSubmit} isPurple title="Login" small={false} />
      </form>
      <Link to="/signup">
        <p className={styles.guest}>Don't have an account? Sign Up</p>
      </Link>
      {isLoading && <CircularProgress />}
      <Snackbar open={snack.open} onClose={() => setSnack({ open: false, message: null })} autoHideDuration={5000}>
        <MuiAlert variant="filled" onClose={() => setSnack({ open: false, message: null })} severity="error">
          {snack.message}
        </MuiAlert>
      </Snackbar>
    </div>
  );
}
Example #24
Source File: RegistrationForm.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
RegistrationForm: React.FC = (props) => {
  const { setCustomerData } = useCustomerData();
  const { setMultiCartData, updateSelectedCart, setMultiCartDataList } = useMultiCartData();
  const { t } = useTranslation();
  const history = useHistory();

  const [registrationErrors, setRegistrationErrors] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const initialValues:FormValues = {
    firstName: '',
    lastName: '',
    email: '',
    emailConfirm: '',
    password: '',
    passwordConfirm: '',
  };

  const validate = (values:FormValues) => {
    const errors:any = {};
    if (!values.firstName) {
      errors.firstName = t('required');
    }
    if (!values.lastName) {
      errors.lastName = t('required');
    }
    if (!values.email) {
      errors.email = t('required');
    }
    if (!values.emailConfirm) {
      errors.emailConfirm = t('required');
    }
    if (values.email && values.emailConfirm && values.email !== values.emailConfirm) {
      errors.emailConfirm = t('email-slash-username-confirm-error');
    }
    if (!values.password) {
      errors.password = t('required');
    }
    if (!values.passwordConfirm) {
      errors.passwordConfirm = t('required');
    }
    if (values.password && values.passwordConfirm && values.password !== values.passwordConfirm) {
      errors.passwordConfirm = t('password-confirm-error');
    }

    return errors;
  };

  const {handleSubmit, handleChange, values, errors} = useFormik({
    initialValues,
    validate,
    onSubmit: (values) => {
      setRegistrationErrors('');
      setIsLoading(true);
      const guestCart = localStorage.getItem('mcart') || '';
      register(`${values.firstName} ${values.lastName}`, values.email, values.password)
        .then(() => {
          login(values.email.toLowerCase(), values.password).then((result) => {
            setIsLoading(false);
            setCustomerData(result.token, result.customer_id);
              addCustomerAssociation(guestCart, result.customer_id, result.token)
              .then(() =>
                getMultiCartsList(result.token, 1).then((res) => {
                  setMultiCartDataList(res.data);
                  getMultiCarts(result.token).then(res => {
                    setMultiCartData(res.data);
                    updateSelectedCart(res.data[0]);
                    localStorage.setItem('mcart', res.data[0].id);                
                  })
                })
                .catch(error => {
                  console.error(error);
                })
              )
              .catch(error => {
                console.error(error);
              });
            history.push('/');
          })
        })
        .catch(error => {
          const errorsContainer = error.errors.map((el:any) => el.detail).join('\n');
          setIsLoading(false);
          setRegistrationErrors(errorsContainer);
          console.error(error);
        });
    },
  });

  return (
    <div className="registrationform container">
      <h1 className="eppagetitle">
        {t('register-new-account')}
      </h1>

      <div className="registrationform__feedback">
        {registrationErrors}
      </div>

      <div className={`registrationform__content ${isLoading ? '--loading' : ''}`}>
        <form className="epform" onSubmit={handleSubmit}>
          {
            (isLoading) ? <div className="epminiLoader --centered" /> : ('')
          }
          <div className={`epform__group ${errors.firstName ? '--error' : ''}`}>
            <label htmlFor="firstName" className="epform__label">
              {t('first-name')} *
            </label>
            <input id="firstName" name="firstName" className="epform__input" type="text" onChange={handleChange} value={values.firstName} />
            <div className="epform__error">
              {errors.firstName ? errors.firstName : null}
            </div>
          </div>
          <div className={`epform__group ${errors.lastName ? '--error' : ''}`}>
            <label htmlFor="lastName" className="epform__label">
              {t('last-name')} *
            </label>
            <input id="lastName" name="lastName" className="epform__input" type="text" onChange={handleChange} value={values.lastName} />
            <div className="epform__error">
              {errors.lastName ? errors.lastName : null}
            </div>
          </div>
          <div className={`epform__group ${errors.email ? '--error' : ''}`}>
            <label htmlFor="email"  className="epform__label">
              {t('email-slash-username')} *
            </label>
            <input id="email" name="email" className="epform__input" type="email" onChange={handleChange} value={values.email} />
            <div className="epform__error">
              {errors.email ? errors.email : null}
            </div>
          </div>
          <div className={`epform__group ${errors.emailConfirm ? '--error' : ''}`}>
            <label htmlFor="email"  className="epform__label">
              {t('email-slash-username-confirmation')} *
            </label>
            <input id="email" name="emailConfirm" className="epform__input" type="email" onChange={handleChange} value={values.emailConfirm} />
            <div className="epform__error">
              {errors.emailConfirm ? errors.emailConfirm : null}
            </div>
          </div>
          <div className={`epform__group ${errors.password ? '--error' : ''}`}>
            <label htmlFor="password" className="epform__label">
              {t('password')} *
            </label>
            <input id="password" name="password" className="epform__input" type="password" onChange={handleChange} value={values.password} />
            <div className="epform__error">
              {errors.password ? errors.password : null}
            </div>
          </div>
          <div className={`epform__group ${errors.passwordConfirm ? '--error' : ''}`}>
            <label htmlFor="passwordConfirm" className="epform__label">
              {t('password-confirmation')} *
            </label>
            <input id="passwordConfirm" name="passwordConfirm" className="epform__input" type="password" onChange={handleChange} value={values.passwordConfirm} />
            <div className="epform__error">
              {errors.passwordConfirm ? errors.passwordConfirm : null}
            </div>
          </div>
          <div className="epform__group --btn-container">
            <button className="epbtn --secondary" id="registration_form_register_button" type="submit" disabled={isLoading}>
              {t('submit')}
            </button>
          </div>
        </form>
      </div>

    </div>
  );
}
Example #25
Source File: Profile.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
Profile: React.FC = (props) => {
  const { id, token, customerEmail, customerName, setEmail, setName } = useCustomerData();
  const { t } = useTranslation();

  const [isEditMode, setIsEditMode] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const { addError } = useContext(APIErrorContext);

  const initialValues:FormValues = {
    email: customerEmail,
    username: customerName,
  };

  const validate = (values:FormValues) => {
    const errors:any = {};
    if (!values.email) {
      errors.email = t('required');
    } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
      errors.email = t('invalid-email');
    }

    if (!values.username) {
      errors.username = t('required');
    }

    return errors;
  };

  const {handleSubmit, handleChange, values, errors, setFieldValue} = useFormik({
    initialValues,
    validate,
    onSubmit: (values) => {
      setIsLoading(true);
      // @ts-ignore
      updateCustomer(id, values.username, values.email, token)
        .then((result) => {
          setIsLoading(false);
          setEmail(result.data.email);
          setName(result.data.name);
          setIsEditMode(false);
        })
        .catch(error => {
          addError(error.errors);
          console.error(error);
        });
    },
  });

  const handleShowForm = () => {
    setIsEditMode(true);
    setFieldValue('email', customerEmail, false);
    setFieldValue('username', customerName, false);
  };

  const handleHideForm = () => {
    setIsEditMode(false);
    errors.email = '';
    errors.username = '';
  };

  return (
    <div className="profile">
      <h1 className="profile__title">{t('my-profile')}</h1>
      <div className="profile__data">
        <p className="profile__titlesmall">{t('general')}</p>
        <div className="profile__container">
          <h2>{t('personal-information')}</h2>
          {!isEditMode ? (
            <div className="profile__info">
              <p className="profile__infoitem">
                <span className="profile__infolabel">{t('email')}:</span>
                {customerEmail}
              </p>
              <p className="profile__infoitem">
                <span className="profile__infolabel">{t('username')}:</span>
                {customerName}
              </p>
              <button className="epbtn" onClick={handleShowForm}>{t('edit')}</button>
            </div>
          ) : (
            <div className={`profile__form ${isLoading ? '--loading' : ''}`}>
              <form className="epform" onSubmit={handleSubmit}>
                {
                  (isLoading) ? <div className="epminiLoader --centered" /> : ('')
                }
                <div className="epform__group">
                  <label className="epform__label" htmlFor="email">{t('email')}:</label>
                  <input type="text" id="email" className="epform__input" onChange={handleChange} value={values.email} />
                  <div className="epform__error">
                    {errors.email ? errors.email : null}
                  </div>
                </div>
                <div className="epform__group">
                  <label className="epform__label" htmlFor="username">{t('username')}:</label>
                  <input type="text" id="username" name="username" className="epform__input" onChange={handleChange} value={values.username} />
                  <div className="epform__error">
                    {errors.username ? errors.username : null}
                  </div>
                </div>
                <div className="epform__group">
                  <button className="epbtn --secondary" type="submit">{t('save')}</button>
                  <button className="epbtn --bordered" type="submit" onClick={handleHideForm}>{t('cancel')}</button>
                </div>
              </form>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}
Example #26
Source File: LoginForm.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
LoginForm: React.FC<LoginFormProps> = (props) => {
  const { handleModalClose,openCartModal, onSubmit, openModal, createCart, handleCloseCartModal, handleShowNewCart } = props;
  const { setCustomerData } = useCustomerData();
  const { t } = useTranslation();
  const { setGuestCartId, setIsCreateNewCart, createDefaultCart  } = useMultiCartData();

  const registrationUrl = createRegistrationUrl();

  const browserHistory = createBrowserHistory();
  const history = useHistory();

  const [failedLogin, setFailedLogin] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  const initialValues:FormValues = {
    emailField: '',
    passwordField: '',
  };

  const validate = (values:any) => {
    const errors:any = {};
    if (!values.emailField) {
      errors.emailField = t('required');
    }
    if (!values.passwordField) {
      errors.passwordField = t('required');
    }

    return errors;
  };

  const {handleSubmit, handleChange, resetForm, values, errors} = useFormik({
    initialValues,
    validate,
    onSubmit: async (values) => {
      const cartId = localStorage.getItem('mcart') || '';
      setGuestCartId(cartId);
      setIsLoading(true);
      login(values.emailField.toLowerCase(), values.passwordField)
        .then((result) => {
          setCustomerData(result.token, result.customer_id);
          setIsLoading(false);
          createDefaultCart();
          if(browserHistory.location.pathname === "/registration")
          {
            history.push('/');
          }
          if (handleModalClose) {
            handleModalClose();
          }
          if(openCartModal && handleShowNewCart){
              openCartModal();
              handleShowNewCart(true);
          }
          if (createCart) {
            setIsCreateNewCart(true);
          }
          if (onSubmit) {
            onSubmit();
          }
        })
        .catch(error => {
          setIsLoading(false);
          setFailedLogin(true);
          console.error(error);
        });
    },
  });

  const registerNewUser = () => {
    if (handleModalClose) {
      handleModalClose();
    }
    if (handleCloseCartModal) {
      handleCloseCartModal();
    }
  };

  useEffect(() => {
    if (!openModal) {
      setFailedLogin(false);
      resetForm();
    }
  }, [openModal, resetForm]);

  return (
    <div className={`loginform${isLoading ? ' --loading' : ''}`}>
      {
        (isLoading) ? <div className="epminiLoader --centered" /> : ('')
      }
      <div className="loginform__feedback">
        {failedLogin ? t('invalid-email-or-password') : ('')}
      </div>
      <form className="epform" id="login_modal_form" onSubmit={handleSubmit}>
        <div className={`epform__group ${errors.emailField ? '--error' : ''}`}>
          <label className="epform__label" htmlFor="emailField">
            {t('email')}:
          </label>
          <input className="epform__input" id="emailField" type="text" onChange={handleChange} value={values.emailField} />
          <div className="epform__error">
            {errors.emailField ? errors.emailField : null}
          </div>
        </div>
        <div className={`epform__group ${errors.passwordField ? '--error' : ''}`}>
          <label className="epform__label" htmlFor="passwordField">
            {t('password')}:
          </label>
          <input className="epform__input" id="passwordField" type="password" onChange={handleChange} value={values.passwordField} />
          <div className="epform__error">
            {errors.passwordField ? errors.passwordField : null}
          </div>
        </div>
        <div className="epform__group --btn-container">
          <button className="epbtn --secondary" id="login_modal_login_button" type="submit" disabled={isLoading}>
            {t('login')}
          </button>
          <Link to={registrationUrl} className="epbtn --secondary" id="login_modal_register_button" onClick={registerNewUser}>
            {t('register')}
          </Link>
        </div>
      </form>
    </div>
  );
}
Example #27
Source File: PasswordLoginForm.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
PasswordLoginForm: React.FC<PasswordLoginFormProps> = (props) => {
  const { handleModalClose,handleCloseCartModal, setIsLoading, setFailedLogin,onSubmit, openCartModal, openModal, createCart, handleShowNewCart  } = props;
  
  const { setCustomerData } = useCustomerData();
  const { t } = useTranslation();
  const registrationUrl = createRegistrationUrl();
  const { setGuestCartId, setIsCreateNewCart, createDefaultCart  } = useMultiCartData();

  const browserHistory = createBrowserHistory();
  const history = useHistory();

  const initialValues:FormValues = {
    emailField: '',
    passwordField: '',
  };

  const validate = (values:any) => {
    const errors:any = {};
    if (!values.emailField) {
      errors.emailField = t('required');
    }
    if (!values.passwordField) {
      errors.passwordField = t('required');
    }

    return errors;
  }

  const {handleSubmit, handleChange,resetForm, values, errors} = useFormik({
    initialValues,
    validate,
    onSubmit: (values) => {
      const cartId = localStorage.getItem('mcart') || '';
      setGuestCartId(cartId);
      setIsLoading(true);
      login(values.emailField.toLowerCase(), values.passwordField)
        .then((result) => {
          setCustomerData(result.token, result.customer_id);
          setIsLoading(false);
          createDefaultCart();
          if(browserHistory.location.pathname === "/registration")
          {
            history.push('/');
          }
          if (handleModalClose) {
            handleModalClose();
          }
          if(openCartModal && handleShowNewCart){
            openCartModal();
            handleShowNewCart(true);
          }
          if (createCart) {
            setIsCreateNewCart(true);
          }
          if (onSubmit) {
            onSubmit();
          }
        })
        .catch(error => {
          setIsLoading(false);
          if(setFailedLogin)
            setFailedLogin(true);
          console.error(error);
        });
    },
  });

  const registerNewUser = () => {
    if (handleModalClose) {
      handleModalClose();
    }
    if (handleCloseCartModal) {
      handleCloseCartModal();
    }
  }
  useEffect(() => {
    if (!openModal && setFailedLogin) {
      setFailedLogin(false);
      resetForm();
    }
  }, [openModal, resetForm, setFailedLogin]);

  return (
        <form className="epform" id="login_modal_form" onSubmit={handleSubmit}>
        
        <div className={`epform__group ${errors.emailField ? '--error' : ''}`}>
            <label className="epform__label" htmlFor="emailField">
            {t('email')}
            </label>
            <input className="epform__input" id="emailField" type="text" onChange={handleChange} value={values.emailField} />
            <div className="epform__error">
            {errors.emailField ? errors.emailField : null}
            </div>
        </div>
        <div className={`epform__group ${errors.passwordField ? '--error' : ''}`}>
            <label className="epform__label" htmlFor="passwordField">
            {t('password')}
            </label>
            <input className="epform__input" id="passwordField" type="password" onChange={handleChange} value={values.passwordField} />
            <div className="epform__error">
            {errors.passwordField ? errors.passwordField : null}
            </div>
        </div>
        
        <div className="epform__group --btn-container">
            <button className="epbtn --primary loginbtn" id="login_modal_login_button" type="submit" disabled={props.isLoading}>
            {t('login')}
            </button>
            <Link to={registrationUrl} className="epbtn --secondary registerbtn" id="login_modal_register_button" onClick={registerNewUser}>
            {t('register')}
            </Link>
        </div>
        </form>
  );
}
Example #28
Source File: BulkOrder.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
BulkOrder: React.FC = (props) => {
  const { t } = useTranslation();
  const { updateCartItems, setCartQuantity, handleShowCartPopup } = useCartData();
  const { multiCartData, updateCartData, updateSelectedCart, setIsCartSelected } = useMultiCartData();
  const { isLoggedIn } = useCustomerData();
  const { selectedLanguage } = useTranslation();
  const { selectedCurrency } = useCurrency();

  const [bulkOrderItems, setBulkOrderItems] = useState([]);
  const [bulkError, setBulkError] = useState('');
  const [showLoader, setShowLoader] = useState(false);
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const [modalOpen, setModalOpen] = useState(false);
  const [cartID, setCartId] = useState("");

  const modalRef = useOnclickOutside(() => {
    setModalOpen(false)
  });

  const dropdownRef = useOnclickOutside(() => {
    setDropdownOpen(false)
  });

  const handleAddToSelectedCart = (cart:any) => {
    updateSelectedCart(cart);
    setCartId(cart.id);
    handleSubmit();
  };

  const handleAddToDefaultCart = () => {
    if (multiCartData && multiCartData.length > 0) {
      handleAddToSelectedCart(multiCartData[0]);
    }
  };

  const CartButton = () => {
    if (isLoggedIn) {
      return (
        <div className="bulkorder__addtocartdropdowncontainer">
          <div className="bulkorder__addtocartdropdownwrap">
            <button
              className="epbtn --primary bulkorder__addtocartbtn"
              onClick={handleAddToDefaultCart}
              disabled={!values.productSKU}
            >
              {t("add-to-cart")}
              {' - '}
              {multiCartData && multiCartData.length > 0 && multiCartData[0].name}
            </button>
            <button onClick={() => setDropdownOpen(!dropdownOpen)} disabled={!values.productSKU} className={`epbtn --primary bulkorder__addtocartdropdowntoggle${
              dropdownOpen ? " --open" : ""
            }`}>
              {showLoader ? (
                <SpinnerIcon className="bulkorder__addtocartdropdownicspinner" />
              ) : (
                <CaretIcon
                  className={`bulkorder__addtocartdropdowniscaret ${
                    dropdownOpen ? "--rotated" : ""
                  }`}
                />
              )}
            </button>
          </div>
          {dropdownOpen ? (
            <div className="bulkorder__addtocartdropdowncontent">
              {multiCartData.slice(1).map((cart: moltin.CartItem) => (
                <button
                  className="bulkorder__addtocartdropdownbtn"
                  key={cart.id}
                  onClick={() => { handleAddToSelectedCart(cart) }}
                >
                  {cart.name}
                </button>
              ))}
              <button
                className="bulkorder__addtocartdropdownbtn"
                key="create-cart-btn"
                onClick={() => setModalOpen(true)}
                type="submit"
              >
                {t('create-new-cart')}
              </button>
            </div>
          ) : null}
        </div>
      );
    }

    return (
      <button className="epbtn --secondary bulkorder__addtocartbtn" type="submit">
        {t("add-to-cart")}
      </button>
    );
  };

  const CreateCartHeader = (
    <div className="bulkorder__createcartheader">
      <span className="bulkorder__createcartheadertext">{t("create-cart")}</span>
      <button
        className="bulkorder__createcartheaderbnt"
        onClick={() => setModalOpen(false)}
      >
        <CloseIcon />
      </button>
    </div>
  );

  const initialValues:FormValues = {
    productSKU: '',
  };

  const {handleSubmit, resetForm, handleChange, values} = useFormik({
    initialValues,
    onSubmit: (values) => {
      setBulkError('');
      setShowLoader(true);
      const currentCart = localStorage.getItem("mcart") || "";
      const mcart = cartID ? cartID : currentCart;
      bulkAdd(mcart, bulkOrderItems, selectedLanguage, selectedCurrency)
        .then((res:any) => {
          const totalQuantity = bulkOrderItems.reduce((sum, { quantity }) => sum + quantity, 0);
          const errorsWQ: [{type:string, sku:string, quantity:number}] = [{type: "", sku: "", quantity: 0}];
          if (res.errors) {
            res.errors.map(function(x:any){
              var result = bulkOrderItems.filter((a:any) => a.sku === x.meta.sku)
              errorsWQ.push(result[0]);
              return errorsWQ;
            })
          }
          const errorsquantity = errorsWQ.reduce((sum, { quantity }) => sum + quantity, 0);
          if (cartID && cartID !== currentCart) {
            localStorage.setItem('mcart', cartID);
          } else {
            updateCartItems();
          }
          updateCartData();
          setCartQuantity(totalQuantity - errorsquantity);
          handleShowCartPopup();
          resetForm();
          setShowLoader(false);
          setIsCartSelected(true);
          setDropdownOpen(false);
          const errorsContainer = res.errors.map((el:any) => (`"${el.meta.sku}" ${el.detail}
          `)).join('\n');
          setBulkError(errorsContainer);
        })
        .catch(error => {
          setShowLoader(false);
          console.error(error);
        });
    }
  });

  useEffect(() => {
    const bulkOrderItems:any = values.productSKU
      .split('\n')
      .filter(l => l.trim().length)
      .map(l => l.split(/[ ,;]+/))
      .map(p => ({ type: 'cart_item', sku: p[0] || '', quantity: isNaN(parseInt(p[1])) ? 1 : parseInt(p[1]) }));
    setBulkOrderItems(bulkOrderItems);
  }, [values.productSKU]);

  const handleClear = () => {
    resetForm();
    setBulkError('');
  };

  const clearError = () => {
    setBulkError('');
  }

  useEffect(() => {
    document.body.style.overflow = modalOpen ? 'hidden' : 'unset';
  }, [modalOpen])

  return (
    <div className="bulkorder">
      <form className="bulkorder__form" onSubmit={handleSubmit}>
        <div className="bulkorder__group">
          <label className="bulkorder__label" htmlFor="productSKU">
            {t('add-products-by-sku')}:
          </label>
          <textarea className="bulkorder__textarea" id="productSKU" rows={5} onChange={handleChange} value={values.productSKU} />
          {values.productSKU &&
            <button className="bulkorder__clearbtn" type="reset" onClick={handleClear}>
              <ClearIcon className="bulkorder__clearicon" />
            </button>
          }
        </div>
        <div className="bulkorder__info">
          <p>{t('bulk-order-format')}</p>
        </div>
        <div className="" ref={dropdownRef}>
          <CartButton/>
        </div>
      </form>
      {
        bulkError &&        
        <div className="bulkorder__messagewrap">
          <button className="bulkorder__clearerrorbtn" type="reset" onClick={clearError} >
              <ClearIcon className="bulkorder__clearerrorbtnicon" />
          </button>
          <div className="bulkorder__feedback">{bulkError}</div>
        </div>
     } 

      {modalOpen ? (
        <div className="bulkorder__createcartmodalbg">
          <div className="bulkorder__createcartmodal" ref={modalRef}>
            <SettingsCart
              title={CreateCartHeader}
              onCartCreate={() => {setModalOpen(false)}}
              handleHideSettings={() => {setModalOpen(false)}}
              setShowCartAlert={() => ''}
            />
          </div>
        </div>
      ) : null}
    </div>
  )
}
Example #29
Source File: AddressForm.tsx    From epcc-react-pwa-reference-storefront with GNU General Public License v3.0 4 votes vote down vote up
AddressForm: React.FC<AddressFormParams> = (props) => {
  const { handleModalClose, isModalOpen, addressData } = props;
  const { t } = useTranslation();
  const [isLoading, setIsLoading] = useState(false);
  const [addressErrors, setAddressErrors] = useState<any[]>([]);
  const { updateAddresses } = useAddressData();

  let initialValues:FormValues = {
    id: addressData?.id ?? '',
    type: addressData?.type ?? '',
    first_name: addressData?.first_name ?? '',
    last_name: addressData?.last_name ?? '',
    line_1: addressData?.line_1 ?? '',
    line_2: addressData?.line_2 ?? '',
    phone_number: addressData?.phone_number ?? '',
    county: addressData?.county ?? '',
    country: addressData?.country ?? '',
    postcode: addressData?.postcode ?? '',
    company_name: addressData?.company_name ?? '',
    city: addressData?.city ?? '',
    name: addressData?.name ?? '',
    instructions: addressData?.instructions ?? '',
  };

  const validate = (values:any) => {
    const errors:any = {};
    if (!values.first_name) {
      errors.first_name = t('required');
    }
    if (!values.last_name) {
      errors.last_name = t('required');
    }
    if (!values.line_1) {
      errors.line_1 = t('required');
    }
    if (!values.country) {
      errors.country = t('required');
    }
    if (!values.county) {
      errors.county = t('required');
    }
    if (!values.postcode) {
      errors.postcode = t('required');
    }
    return errors;
  };

  const handleClose = () => {
    handleModalClose();
    resetForm({})
  };

  const {handleSubmit, handleChange, resetForm, values, errors} = useFormik({
    initialValues,
    validate,
    onSubmit: (values) => {
      setIsLoading(true);
      const data = values;
      const token = localStorage.getItem('mtoken') || '';
      const customer = localStorage.getItem('mcustomer') || '';
      if(values.id) {
        updateAddress(customer, values.id, data, token)
          .then(() => {
            updateAddresses();
            handleClose();
            setIsLoading(false);
          })
          .catch(error => {
            setIsLoading(false);
            setAddressErrors(error.errors);
            console.error(error);
          });
      } else {
        addNewAddress(customer, data, token)
        .then(() => {
          updateAddresses();
          handleModalClose();
          setIsLoading(false);
        })
        .catch(error => {
          setIsLoading(false);
          setAddressErrors(error.errors);
          console.error(error);
        });
      }
    },
  });

  return (
    <Modal open={isModalOpen} onClose={handleClose} classNames={{modal: 'addressform'}} showCloseIcon={false}>
      {
        (isLoading) ? <div className="epminiLoader --centered"/> : ('')
      }
      <div className={`addressform__content ${isLoading ? '--loading' : ''}`}>
        <div className="addressform__header">
          <h2 className="addressform__title">
            {values.id ? t('edit-address') : t('new-address')}
          </h2>
          <button type="button" aria-label="close" onClick={handleModalClose}>
            <CloseIcon/>
          </button>
        </div>
        <div className="addressform__body">
          <div className="addressform__feedback">
            {addressErrors.length ? (
              addressErrors.map(error => (
                <div key={error.detail}>
                  {error.detail}
                </div>
              ))
              ) : ('')}
          </div>
          <form className="epform --addressform" id="address_modal_form" onSubmit={handleSubmit}>
            <div className={`epform__group ${errors.first_name ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="first_name">
                <span className="required-label">
                  *
                </span>
                &nbsp;
                {t('first-name')}
              </label>
              <input className="epform__input" id="first_name" type="text" onChange={handleChange} value={values.first_name} />
              <div className="epform__error">
                {errors.first_name ? errors.first_name : null}
              </div>
            </div>
            <div className={`epform__group ${errors.last_name ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="last_name">
                <span className="required-label">
                  *
                </span>
                &nbsp;
                {t('last-name')}
              </label>
              <input className="epform__input" id="last_name" type="text" onChange={handleChange} value={values.last_name} />
              <div className="epform__error">
                {errors.last_name ? errors.last_name : null}
              </div>
            </div>
            <div className={`epform__group ${errors.name ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="name">
                {t('name')}
              </label>
              <input className="epform__input" id="name" type="text" onChange={handleChange} value={values.name} />
              <div className="epform__error">
                {errors.name ? errors.name : null}
              </div>
            </div>
            <div className={`epform__group ${errors.company_name ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="company_name">
                {t('company-name')}
              </label>
              <input className="epform__input" id="company_name" type="text" onChange={handleChange} value={values.company_name} />
              <div className="epform__error">
                {errors.company_name ? errors.company_name : null}
              </div>
            </div>
            <div className={`epform__group ${errors.line_1 ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="line_1">
                <span className="required-label">
                  *
                </span>
                &nbsp;
                {t('street-address')}
              </label>
              <input className="epform__input" id="line_1" type="text" onChange={handleChange} value={values.line_1} />
              <div className="epform__error">
                {errors.line_1 ? errors.line_1 : null}
              </div>
            </div>
            <div className={`epform__group ${errors.line_2 ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="line_2">
                {t('extended-address')}
              </label>
              <input className="epform__input" id="line_2" type="text" onChange={handleChange} value={values.line_2} />
              <div className="epform__error">
                {errors.line_2 ? errors.line_2 : null}
              </div>
            </div>
            <div className={`epform__group ${errors.county ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="county">
                <span className="required-label">
                  *
                </span>
                &nbsp;
                {t('county')}
              </label>
              <input className="epform__input" id="county" type="text" onChange={handleChange} value={values.county} />
              <div className="epform__error">
                {errors.county ? errors.county : null}
              </div>
            </div>
            <div className={`epform__group ${errors.country ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="country">
                <span className="required-label">
                  *
                </span>
                &nbsp;
                {t('country')}
              </label>
              <CountriesSelect value={values.country} onChange={handleChange} />
              <div className="epform__error">
                {errors.country ? errors.country : null}
              </div>
            </div>
            <div className={`epform__group ${errors.phone_number ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="phone_number">
                {t('phone-number')}
              </label>
              <input className="epform__input" id="phone_number" type="text" onChange={handleChange} value={values.phone_number} />
              <div className="epform__error">
                {errors.phone_number ? errors.phone_number : null}
              </div>
            </div>
            <div className={`epform__group ${errors.city ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="city">
                {t('city')}
              </label>
              <input className="epform__input" id="city" type="text" onChange={handleChange} value={values.city} />
              <div className="epform__error">
                {errors.city ? errors.city : null}
              </div>
            </div>
            <div className={`epform__group ${errors.postcode ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="postcode">
                <span className="required-label">
                  *
                </span>
                &nbsp;
                {t('postal-сode')}
              </label>
              <input className="epform__input" id="postcode" type="text" onChange={handleChange} value={values.postcode} />
              <div className="epform__error">
                {errors.postcode ? errors.postcode : null}
              </div>
            </div>
            <div className={`epform__group ${errors.instructions ? '--error' : ''}`}>
              <label className="epform__label" htmlFor="instructions">
                {t('instructions')}
              </label>
              <input className="epform__input" id="instructions" type="text" onChange={handleChange} value={values.instructions} />
              <div className="epform__error">
                {errors.instructions ? errors.instructions : null}
              </div>
            </div>
            <div className="epform__group --btncontainer">
              <button className="epbtn --bordered" type="button" onClick={handleClose}>
                {t('cancel')}
              </button>
              <button className="epbtn --secondary" type="submit">
                {t('save')}
              </button>
            </div>
          </form>
        </div>
      </div>
    </Modal>
  )
}