|
|
@@ -1,7 +1,8 @@
|
|
|
-import type { FormEventHandler } from 'react';
|
|
|
import React from 'react';
|
|
|
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
+import { useForm } from 'react-hook-form';
|
|
|
+
|
|
|
|
|
|
import type { IAccessTokenInfo } from '~/interfaces/access-token';
|
|
|
|
|
|
@@ -11,6 +12,13 @@ type AccessTokenFormProps = {
|
|
|
submitHandler: (info: IAccessTokenInfo) => Promise<void>;
|
|
|
}
|
|
|
|
|
|
+type FormInputs = {
|
|
|
+ expiredAt: string;
|
|
|
+ description: string;
|
|
|
+ // TODO: Implement scope selection
|
|
|
+ // scopes: string[];
|
|
|
+}
|
|
|
+
|
|
|
// TODO: Implement scope selection
|
|
|
export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Element => {
|
|
|
const { submitHandler } = props;
|
|
|
@@ -18,17 +26,27 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
|
|
|
|
|
|
const defaultExpiredAt = new Date();
|
|
|
defaultExpiredAt.setMonth(defaultExpiredAt.getMonth() + 1);
|
|
|
+ const defaultExpiredAtStr = defaultExpiredAt.toISOString().split('T')[0];
|
|
|
+ const todayStr = new Date().toISOString().split('T')[0];
|
|
|
|
|
|
- const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
|
|
|
- e.preventDefault();
|
|
|
- const form = new FormData(e.currentTarget);
|
|
|
- const expiredAtDate = new Date(form.get('expiredAt') as string);
|
|
|
- const description = form.get('description') as string;
|
|
|
+ const {
|
|
|
+ register,
|
|
|
+ handleSubmit,
|
|
|
+ formState: { errors, isValid },
|
|
|
+ } = useForm<FormInputs>({
|
|
|
+ defaultValues: {
|
|
|
+ expiredAt: defaultExpiredAtStr,
|
|
|
+ description: '',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const onSubmit = (data: FormInputs) => {
|
|
|
+ const expiredAtDate = new Date(data.expiredAt);
|
|
|
const scope = []; // TODO: Implement scope selection
|
|
|
|
|
|
submitHandler({
|
|
|
expiredAt: expiredAtDate,
|
|
|
- description,
|
|
|
+ description: data.description,
|
|
|
scope,
|
|
|
});
|
|
|
};
|
|
|
@@ -37,7 +55,7 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
|
|
|
<div className="card mt-3 mb-4">
|
|
|
<div className="card-header">{t('page_me_access_token.form.title')}</div>
|
|
|
<div className="card-body">
|
|
|
- <form onSubmit={handleSubmit}>
|
|
|
+ <form onSubmit={handleSubmit(onSubmit)}>
|
|
|
<div className="mb-3">
|
|
|
<label htmlFor="expiredAt" className="form-label">{t('page_me_access_token.expiredAt')}</label>
|
|
|
<div className="row">
|
|
|
@@ -45,14 +63,24 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
|
|
|
<div className="input-group">
|
|
|
<input
|
|
|
type="date"
|
|
|
- className="form-control"
|
|
|
+ className={`form-control ${errors.expiredAt ? 'is-invalid' : ''}`}
|
|
|
data-testid="grw-accesstoken-input-expiredAt"
|
|
|
- name="expiredAt"
|
|
|
- min={new Date().toISOString().split('T')[0]}
|
|
|
- required
|
|
|
- defaultValue={defaultExpiredAt.toISOString().split('T')[0]}
|
|
|
+ min={todayStr}
|
|
|
+ {...register('expiredAt', {
|
|
|
+ required: t('input_validation.message.required', { param: t('page_me_access_token.expiredAt') }),
|
|
|
+ validate: (value) => {
|
|
|
+ const date = new Date(value);
|
|
|
+ const now = new Date();
|
|
|
+ return date > now || 'Expiration date must be in the future';
|
|
|
+ },
|
|
|
+ })}
|
|
|
/>
|
|
|
</div>
|
|
|
+ {errors.expiredAt && (
|
|
|
+ <div className="invalid-feedback d-block">
|
|
|
+ {errors.expiredAt.message}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="form-text">{t('page_me_access_token.form.expiredAt_desc')}</div>
|
|
|
@@ -61,14 +89,22 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
|
|
|
<div className="mb-3">
|
|
|
<label htmlFor="description" className="form-label">{t('page_me_access_token.description')}</label>
|
|
|
<textarea
|
|
|
- className="form-control"
|
|
|
- name="description"
|
|
|
- maxLength={MAX_DESCRIPTION_LENGTH}
|
|
|
+ className={`form-control ${errors.description ? 'is-invalid' : ''}`}
|
|
|
rows={3}
|
|
|
- required
|
|
|
- defaultValue=""
|
|
|
data-testid="grw-accesstoken-textarea-description"
|
|
|
+ {...register('description', {
|
|
|
+ required: t('input_validation.message.required', { param: t('page_me_access_token.description') }),
|
|
|
+ maxLength: {
|
|
|
+ value: MAX_DESCRIPTION_LENGTH,
|
|
|
+ message: t('page_me_access_token.form.description_max_length', { length: MAX_DESCRIPTION_LENGTH }),
|
|
|
+ },
|
|
|
+ })}
|
|
|
/>
|
|
|
+ {errors.description && (
|
|
|
+ <div className="invalid-feedback">
|
|
|
+ {errors.description.message}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
<div className="form-text">{t('page_me_access_token.form.description_desc')}</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -82,6 +118,7 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
|
|
|
type="submit"
|
|
|
className="btn btn-primary"
|
|
|
data-testid="grw-accesstoken-create-button"
|
|
|
+ disabled={!isValid}
|
|
|
>
|
|
|
{t('page_me_access_token.create_token')}
|
|
|
</button>
|