AccessTokenForm.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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((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. watch,
  28. } = useForm<FormInputs>({
  29. defaultValues: {
  30. expiredAt: defaultExpiredAtStr,
  31. description: '',
  32. scopes: [],
  33. },
  34. });
  35. const onSubmit = (data: FormInputs) => {
  36. const expiredAtDate = new Date(data.expiredAt);
  37. expiredAtDate.setHours(23, 59, 59, 999);
  38. const scopes: Scope[] = data.scopes ? data.scopes : [];
  39. submitHandler({
  40. expiredAt: expiredAtDate,
  41. description: data.description,
  42. scopes,
  43. });
  44. };
  45. return (
  46. <div className="card mt-3 mb-4">
  47. <div className="card-header">{t('page_me_access_token.form.title')}</div>
  48. <div className="card-body">
  49. <form onSubmit={handleSubmit(onSubmit)}>
  50. <div className="mb-3">
  51. <label htmlFor="expiredAt" className="form-label">{t('page_me_access_token.expiredAt')}</label>
  52. <div className="row">
  53. <div className="col-16 col-sm-4 col-md-4 col-lg-3">
  54. <div className="input-group">
  55. <input
  56. type="date"
  57. className={`form-control ${errors.expiredAt ? 'is-invalid' : ''}`}
  58. data-testid="grw-accesstoken-input-expiredAt"
  59. min={todayStr}
  60. {...register('expiredAt', {
  61. required: t('input_validation.message.required', { param: t('page_me_access_token.expiredAt') }),
  62. })}
  63. />
  64. </div>
  65. {errors.expiredAt && (
  66. <div className="invalid-feedback d-block">
  67. {errors.expiredAt.message}
  68. </div>
  69. )}
  70. </div>
  71. </div>
  72. <div className="form-text">{t('page_me_access_token.form.expiredAt_desc')}</div>
  73. </div>
  74. <div className="mb-3">
  75. <label htmlFor="description" className="form-label">{t('page_me_access_token.description')}</label>
  76. <textarea
  77. className={`form-control ${errors.description ? 'is-invalid' : ''}`}
  78. rows={3}
  79. data-testid="grw-accesstoken-textarea-description"
  80. {...register('description', {
  81. required: t('input_validation.message.required', { param: t('page_me_access_token.description') }),
  82. maxLength: {
  83. value: MAX_DESCRIPTION_LENGTH,
  84. message: t('page_me_access_token.form.description_max_length', { length: MAX_DESCRIPTION_LENGTH }),
  85. },
  86. })}
  87. />
  88. {errors.description && (
  89. <div className="invalid-feedback">
  90. {errors.description.message}
  91. </div>
  92. )}
  93. <div className="form-text">{t('page_me_access_token.form.description_desc')}</div>
  94. </div>
  95. <div className="mb-3">
  96. <label htmlFor="scopes" className="form-label">
  97. {t('page_me_access_token.scope')}
  98. </label>
  99. <AccessTokenScopeSelect
  100. selectedScopes={watch('scopes')}
  101. register={register('scopes', {
  102. required: t('input_validation.message.required', { param: t('page_me_access_token.scope') }),
  103. })}
  104. />
  105. {errors.scopes && (
  106. <div className="invalid-feedback">
  107. {errors.scopes.message}
  108. </div>
  109. )}
  110. <div className="form-text mb-2">
  111. {t('page_me_access_token.form.scope_desc')}
  112. </div>
  113. </div>
  114. <button
  115. type="submit"
  116. className="btn btn-primary"
  117. data-testid="grw-accesstoken-create-button"
  118. disabled={!isValid}
  119. >
  120. {t('page_me_access_token.create_token')}
  121. </button>
  122. </form>
  123. </div>
  124. </div>
  125. );
  126. });
  127. AccessTokenForm.displayName = 'AccessTokenForm';