Преглед изворни кода

add token display and copy functionality to access token generation

reiji-h пре 1 година
родитељ
комит
8a0ded4724
1 измењених фајлова са 73 додато и 10 уклоњено
  1. 73 10
      apps/app/src/client/components/Me/ApiSettings.tsx

+ 73 - 10
apps/app/src/client/components/Me/ApiSettings.tsx

@@ -1,6 +1,7 @@
 import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
+import CopyToClipboard from 'react-copy-to-clipboard';
 
 import {
   apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
@@ -139,15 +140,21 @@ const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Element =>
         <form onSubmit={handleSubmit}>
           <div className="mb-3">
             <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>
 
@@ -215,10 +222,22 @@ const AccessTokenSettings = React.memo((): JSX.Element => {
     setIsOpen(prev => !prev);
   }, []);
 
+  // State to store the newly generated token
+  const [newToken, setNewToken] = React.useState<string | null>(null);
+
   const {
     data: accessTokens, mutate, generateAccessToken, deleteAccessToken, deleteAllAccessTokens,
   } = 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 で共通化
   type GenerateAccessTokenInfo = {
@@ -228,10 +247,15 @@ const AccessTokenSettings = React.memo((): JSX.Element => {
   }
   const submitHandler = useCallback(async(info: GenerateAccessTokenInfo) => {
     try {
-      await generateAccessToken(info);
+      const result = await generateAccessToken(info);
       mutate();
       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' }));
     }
     catch (err) {
@@ -253,6 +277,45 @@ const AccessTokenSettings = React.memo((): JSX.Element => {
   return (
     <>
       <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">
           <table className="table table-bordered">
             <thead>