AccessTokenForm.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import React from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import { useForm } from 'react-hook-form';
  4. import type { IAccessTokenInfo } from '~/interfaces/access-token';
  5. const MAX_DESCRIPTION_LENGTH = 200;
  6. type AccessTokenFormProps = {
  7. submitHandler: (info: IAccessTokenInfo) => Promise<void>;
  8. }
  9. type FormInputs = {
  10. expiredAt: string;
  11. description: string;
  12. // TODO: Implement scope selection
  13. // scopes: string[];
  14. }
  15. // TODO: Implement scope selection
  16. export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Element => {
  17. const { submitHandler } = props;
  18. const { t } = useTranslation();
  19. const defaultExpiredAt = new Date();
  20. defaultExpiredAt.setMonth(defaultExpiredAt.getMonth() + 1);
  21. const defaultExpiredAtStr = defaultExpiredAt.toISOString().split('T')[0];
  22. const todayStr = new Date().toISOString().split('T')[0];
  23. const {
  24. register,
  25. handleSubmit,
  26. formState: { errors, isValid },
  27. } = useForm<FormInputs>({
  28. defaultValues: {
  29. expiredAt: defaultExpiredAtStr,
  30. description: '',
  31. },
  32. });
  33. const onSubmit = (data: FormInputs) => {
  34. const expiredAtDate = new Date(data.expiredAt);
  35. const scope = []; // TODO: Implement scope selection
  36. submitHandler({
  37. expiredAt: expiredAtDate,
  38. description: data.description,
  39. scope,
  40. });
  41. };
  42. return (
  43. <div className="card mt-3 mb-4">
  44. <div className="card-header">{t('page_me_access_token.form.title')}</div>
  45. <div className="card-body">
  46. <form onSubmit={handleSubmit(onSubmit)}>
  47. <div className="mb-3">
  48. <label htmlFor="expiredAt" className="form-label">{t('page_me_access_token.expiredAt')}</label>
  49. <div className="row">
  50. <div className="col-16 col-sm-4 col-md-4 col-lg-3">
  51. <div className="input-group">
  52. <input
  53. type="date"
  54. className={`form-control ${errors.expiredAt ? 'is-invalid' : ''}`}
  55. data-testid="grw-accesstoken-input-expiredAt"
  56. min={todayStr}
  57. {...register('expiredAt', {
  58. required: t('input_validation.message.required', { param: t('page_me_access_token.expiredAt') }),
  59. validate: (value) => {
  60. const date = new Date(value);
  61. const now = new Date();
  62. return date > now || 'Expiration date must be in the future';
  63. },
  64. })}
  65. />
  66. </div>
  67. {errors.expiredAt && (
  68. <div className="invalid-feedback d-block">
  69. {errors.expiredAt.message}
  70. </div>
  71. )}
  72. </div>
  73. </div>
  74. <div className="form-text">{t('page_me_access_token.form.expiredAt_desc')}</div>
  75. </div>
  76. <div className="mb-3">
  77. <label htmlFor="description" className="form-label">{t('page_me_access_token.description')}</label>
  78. <textarea
  79. className={`form-control ${errors.description ? 'is-invalid' : ''}`}
  80. rows={3}
  81. data-testid="grw-accesstoken-textarea-description"
  82. {...register('description', {
  83. required: t('input_validation.message.required', { param: t('page_me_access_token.description') }),
  84. maxLength: {
  85. value: MAX_DESCRIPTION_LENGTH,
  86. message: t('page_me_access_token.form.description_max_length', { length: MAX_DESCRIPTION_LENGTH }),
  87. },
  88. })}
  89. />
  90. {errors.description && (
  91. <div className="invalid-feedback">
  92. {errors.description.message}
  93. </div>
  94. )}
  95. <div className="form-text">{t('page_me_access_token.form.description_desc')}</div>
  96. </div>
  97. <div className="mb-3">
  98. <label htmlFor="scope" className="form-label">{t('page_me_access_token.scope')}</label>
  99. <div className="form-text mb-2">{t('page_me_access_token.form.scope_desc')}</div>
  100. <div className="form-text mb-2">(TBD)</div>
  101. </div>
  102. <button
  103. type="submit"
  104. className="btn btn-primary"
  105. data-testid="grw-accesstoken-create-button"
  106. disabled={!isValid}
  107. >
  108. {t('page_me_access_token.create_token')}
  109. </button>
  110. </form>
  111. </div>
  112. </div>
  113. );
  114. });
  115. AccessTokenForm.displayName = 'AccessTokenForm';