AccessTokenForm.tsx 5.3 KB

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