|
@@ -1,6 +1,7 @@
|
|
|
import React, { useCallback } from 'react';
|
|
import React, { useCallback } from 'react';
|
|
|
|
|
|
|
|
import { useTranslation } from 'next-i18next';
|
|
import { useTranslation } from 'next-i18next';
|
|
|
|
|
+import CopyToClipboard from 'react-copy-to-clipboard';
|
|
|
|
|
|
|
|
import {
|
|
import {
|
|
|
apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
|
|
apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
|
|
@@ -139,15 +140,21 @@ const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Element =>
|
|
|
<form onSubmit={handleSubmit}>
|
|
<form onSubmit={handleSubmit}>
|
|
|
<div className="mb-3">
|
|
<div className="mb-3">
|
|
|
<label htmlFor="expiredAt" className="form-label">{t('Expiration Date')}</label>
|
|
<label htmlFor="expiredAt" className="form-label">{t('Expiration Date')}</label>
|
|
|
- <input
|
|
|
|
|
- type="date"
|
|
|
|
|
- className="form-control"
|
|
|
|
|
- id="expiredAt"
|
|
|
|
|
- value={expiredAt}
|
|
|
|
|
- min={today}
|
|
|
|
|
- onChange={e => setExpiredAt(e.target.value)}
|
|
|
|
|
- required
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div className="row">
|
|
|
|
|
+ <div className="col-16 col-sm-4 col-md-4 col-lg-3">
|
|
|
|
|
+ <div className="input-group">
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="date"
|
|
|
|
|
+ className="form-control"
|
|
|
|
|
+ id="expiredAt"
|
|
|
|
|
+ value={expiredAt}
|
|
|
|
|
+ min={today}
|
|
|
|
|
+ onChange={e => setExpiredAt(e.target.value)}
|
|
|
|
|
+ required
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
<div className="form-text">{t('Select when this access token should expire')}</div>
|
|
<div className="form-text">{t('Select when this access token should expire')}</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -215,10 +222,22 @@ const AccessTokenSettings = React.memo((): JSX.Element => {
|
|
|
setIsOpen(prev => !prev);
|
|
setIsOpen(prev => !prev);
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
+ // State to store the newly generated token
|
|
|
|
|
+ const [newToken, setNewToken] = React.useState<string | null>(null);
|
|
|
|
|
+
|
|
|
const {
|
|
const {
|
|
|
data: accessTokens, mutate, generateAccessToken, deleteAccessToken, deleteAllAccessTokens,
|
|
data: accessTokens, mutate, generateAccessToken, deleteAccessToken, deleteAllAccessTokens,
|
|
|
} = useSWRxAccessToken();
|
|
} = useSWRxAccessToken();
|
|
|
|
|
|
|
|
|
|
+ // Function to hide the token display
|
|
|
|
|
+ const closeTokenDisplay = useCallback(() => {
|
|
|
|
|
+ setNewToken(null);
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ // Handle successful copy
|
|
|
|
|
+ const handleCopySuccess = useCallback(() => {
|
|
|
|
|
+ toastSuccess(t('Copied to clipboard'));
|
|
|
|
|
+ }, [t]);
|
|
|
|
|
|
|
|
// TODO: model で共通化
|
|
// TODO: model で共通化
|
|
|
type GenerateAccessTokenInfo = {
|
|
type GenerateAccessTokenInfo = {
|
|
@@ -228,10 +247,15 @@ const AccessTokenSettings = React.memo((): JSX.Element => {
|
|
|
}
|
|
}
|
|
|
const submitHandler = useCallback(async(info: GenerateAccessTokenInfo) => {
|
|
const submitHandler = useCallback(async(info: GenerateAccessTokenInfo) => {
|
|
|
try {
|
|
try {
|
|
|
- await generateAccessToken(info);
|
|
|
|
|
|
|
+ const result = await generateAccessToken(info);
|
|
|
mutate();
|
|
mutate();
|
|
|
setIsOpen(false); // Close form after successful submission
|
|
setIsOpen(false); // Close form after successful submission
|
|
|
|
|
|
|
|
|
|
+ // Store the newly generated token to display to the user
|
|
|
|
|
+ if (result?.token) {
|
|
|
|
|
+ setNewToken(result.token);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
toastSuccess(t('toaster.update_successed', { target: t('page_me_access_token.access_token'), ns: 'commons' }));
|
|
toastSuccess(t('toaster.update_successed', { target: t('page_me_access_token.access_token'), ns: 'commons' }));
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
@@ -253,6 +277,45 @@ const AccessTokenSettings = React.memo((): JSX.Element => {
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
<div className="container p-0">
|
|
<div className="container p-0">
|
|
|
|
|
+ {/* Token Display Area (non-modal) */}
|
|
|
|
|
+ {newToken && (
|
|
|
|
|
+ <div className="alert alert-warning mb-4" role="alert">
|
|
|
|
|
+ <div className="d-flex justify-content-between align-items-center mb-2">
|
|
|
|
|
+ <h5 className="mb-0">
|
|
|
|
|
+ <i className="fa fa-exclamation-triangle me-2" aria-hidden="true"></i>
|
|
|
|
|
+ {t('New Access Token')}
|
|
|
|
|
+ </h5>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ className="btn-close"
|
|
|
|
|
+ onClick={closeTokenDisplay}
|
|
|
|
|
+ aria-label="Close"
|
|
|
|
|
+ >
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <p className="fw-bold mb-2">{t('This token will only be displayed once. Please save it securely.')}</p>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="input-group mb-2">
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ className="form-control font-monospace"
|
|
|
|
|
+ value={newToken}
|
|
|
|
|
+ readOnly
|
|
|
|
|
+ data-vrt-blackout
|
|
|
|
|
+ />
|
|
|
|
|
+ <CopyToClipboard text={newToken} onCopy={handleCopySuccess}>
|
|
|
|
|
+ <button
|
|
|
|
|
+ className="btn btn-outline-secondary"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span className="material-symbols-outlined">content_copy</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </CopyToClipboard>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
<div className="table-responsive">
|
|
<div className="table-responsive">
|
|
|
<table className="table table-bordered">
|
|
<table className="table table-bordered">
|
|
|
<thead>
|
|
<thead>
|