Skip to content

Commit 7ffe8cc

Browse files
committed
[pat] add loading state
1 parent fa06d68 commit 7ffe8cc

File tree

1 file changed

+137
-115
lines changed

1 file changed

+137
-115
lines changed

components/dashboard/src/settings/PersonalAccessTokens.tsx

Lines changed: 137 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ContextMenuEntry } from "../components/ContextMenu";
2828
import PillLabel from "../components/PillLabel";
2929
import dayjs from "dayjs";
3030
import { ItemFieldContextMenu } from "../components/ItemsList";
31+
import { SpinnerLoader, SpinnerOverlayLoader } from "../components/Loader";
3132

3233
export default function PersonalAccessTokens() {
3334
const { enablePersonalAccessTokens } = useContext(FeatureFlagContext);
@@ -182,6 +183,7 @@ export function PersonalAccessTokenCreateView() {
182183
const params = useParams<{ tokenId?: string }>();
183184
const history = useHistory<TokenInfo>();
184185

186+
const [loading, setLoading] = useState(false);
185187
const [errorMsg, setErrorMsg] = useState("");
186188
const [editTokenID, setEditTokenID] = useState<string>();
187189
const [editToken, setEditToken] = useState<PersonalAccessToken>();
@@ -209,7 +211,7 @@ export function PersonalAccessTokenCreateView() {
209211
}
210212
// update UI to `edit style` immediately
211213
setEditTokenID(tokenId);
212-
214+
setLoading(true);
213215
const resp = await personalAccessTokensService.getPersonalAccessToken({ id: tokenId });
214216
const token = resp.token!;
215217
setEditToken(token);
@@ -220,6 +222,7 @@ export function PersonalAccessTokenCreateView() {
220222
} catch (e) {
221223
setErrorMsg(e.message);
222224
}
225+
setLoading(false);
223226
})();
224227
}, []);
225228

@@ -331,82 +334,88 @@ export function PersonalAccessTokenCreateView() {
331334
/>
332335
)}
333336
</>
334-
<div className="max-w-md mb-6">
335-
<div className="flex flex-col mb-4">
336-
<h3>{editTokenID ? "Edit" : "New"} Personal Access Token</h3>
337-
{editTokenID ? (
338-
<h2 className="text-gray-500 dark:text-gray-400">
339-
Update token name, expiration date, permissions, or regenerate token.
340-
</h2>
341-
) : (
342-
<h2 className="text-gray-500 dark:text-gray-400">Create a new access token.</h2>
343-
)}
344-
</div>
345-
<div className="flex flex-col gap-4">
346-
<div>
347-
<h4>Token Name</h4>
348-
<input
349-
className="w-full"
350-
value={token.name}
351-
onChange={(e) => update({ name: e.target.value })}
352-
onKeyDown={(e) => {
353-
if (e.key === "Enter") {
354-
e.preventDefault();
355-
handleConfirm();
356-
}
357-
}}
358-
type="text"
359-
placeholder="Token Name"
360-
/>
361-
<p className="text-gray-500 dark:text-gray-400 mt-2">
362-
The application name using the token or the purpose of the token.
363-
</p>
337+
<SpinnerOverlayLoader content="loading access token" loading={loading}>
338+
<div className="mb-6">
339+
<div className="flex flex-col mb-4">
340+
<h3>{editTokenID ? "Edit" : "New"} Personal Access Token</h3>
341+
{editTokenID ? (
342+
<h2 className="text-gray-500 dark:text-gray-400">
343+
Update token name, expiration date, permissions, or regenerate token.
344+
</h2>
345+
) : (
346+
<h2 className="text-gray-500 dark:text-gray-400">
347+
Create a new access token.
348+
</h2>
349+
)}
364350
</div>
365-
{!editTokenID && (
366-
<DateSelector
367-
title="Expiration Date"
368-
description={`The token will expire on ${dayjs(token.expirationDate).format(
369-
"MMM D, YYYY",
370-
)}`}
371-
options={TokenExpirationDays}
372-
value={TokenExpirationDays.find((i) => i.value === token.expirationDays)?.value}
373-
onChange={(value) => {
374-
update({ expirationDays: value });
375-
}}
376-
/>
377-
)}
378-
<div>
379-
<h4>Permission</h4>
380-
<div className="space-y-2">
381-
{AllPermissions.map((item) => (
382-
<CheckBox
383-
className=""
384-
title={item.name}
385-
desc={item.description}
386-
checked={item.scopes.every((s) => token.scopes.has(s))}
387-
onChange={(e) => {
388-
if (e.target.checked) {
389-
update({}, item.scopes);
390-
} else {
391-
update({}, undefined, item.scopes);
392-
}
393-
}}
394-
/>
395-
))}
351+
<div className="flex flex-col gap-4">
352+
<div>
353+
<h4>Token Name</h4>
354+
<input
355+
className="max-w-md"
356+
value={token.name}
357+
onChange={(e) => update({ name: e.target.value })}
358+
onKeyDown={(e) => {
359+
if (e.key === "Enter") {
360+
e.preventDefault();
361+
handleConfirm();
362+
}
363+
}}
364+
type="text"
365+
placeholder="Token Name"
366+
/>
367+
<p className="text-gray-500 dark:text-gray-400 mt-2">
368+
The application name using the token or the purpose of the token.
369+
</p>
370+
</div>
371+
{!editTokenID && (
372+
<DateSelector
373+
title="Expiration Date"
374+
description={`The token will expire on ${dayjs(token.expirationDate).format(
375+
"MMM D, YYYY",
376+
)}`}
377+
options={TokenExpirationDays}
378+
value={TokenExpirationDays.find((i) => i.value === token.expirationDays)?.value}
379+
onChange={(value) => {
380+
update({ expirationDays: value });
381+
}}
382+
/>
383+
)}
384+
<div>
385+
<h4>Permission</h4>
386+
<div className="space-y-2">
387+
{AllPermissions.map((item) => (
388+
<CheckBox
389+
className=""
390+
title={item.name}
391+
desc={item.description}
392+
checked={item.scopes.every((s) => token.scopes.has(s))}
393+
onChange={(e) => {
394+
if (e.target.checked) {
395+
update({}, item.scopes);
396+
} else {
397+
update({}, undefined, item.scopes);
398+
}
399+
}}
400+
/>
401+
))}
402+
</div>
396403
</div>
397404
</div>
398405
</div>
399-
</div>
400-
<div className="flex gap-2">
401-
{editTokenID && (
402-
<Link to={settingsPathPersonalAccessTokens}>
403-
<button className="secondary" onClick={handleConfirm}>
404-
Cancel
405-
</button>
406-
</Link>
407-
)}
408-
<button onClick={handleConfirm}>{editTokenID ? "Update" : "Create"} Personal Access Token</button>
409-
</div>
406+
<div className="flex gap-2">
407+
{editTokenID && (
408+
<Link to={settingsPathPersonalAccessTokens}>
409+
<button className="secondary" onClick={handleConfirm}>
410+
Cancel
411+
</button>
412+
</Link>
413+
)}
414+
<button onClick={handleConfirm}>
415+
{editTokenID ? "Update" : "Create"} Personal Access Token
416+
</button>
417+
</div>
418+
</SpinnerOverlayLoader>
410419
</PageWithSettingsSubMenu>
411420
</div>
412421
);
@@ -473,18 +482,21 @@ interface TokenInfo {
473482
function ListAccessTokensView() {
474483
const location = useLocation();
475484

485+
const [loading, setLoading] = useState<boolean>(false);
476486
const [tokens, setTokens] = useState<PersonalAccessToken[]>([]);
477487
const [tokenInfo, setTokenInfo] = useState<TokenInfo>();
478488
const [modalData, setModalData] = useState<{ token: PersonalAccessToken; action: TokenAction }>();
479489
const [errorMsg, setErrorMsg] = useState("");
480490

481491
async function loadTokens() {
482492
try {
493+
setLoading(true);
483494
const response = await personalAccessTokensService.listPersonalAccessTokens({});
484495
setTokens(response.tokens);
485496
} catch (e) {
486497
setErrorMsg(e.message);
487498
}
499+
setLoading(false);
488500
}
489501

490502
useEffect(() => {
@@ -594,52 +606,62 @@ function ListAccessTokensView() {
594606
</>
595607
)}
596608
</>
597-
{tokens.length === 0 ? (
598-
<div className="bg-gray-100 dark:bg-gray-800 rounded-xl w-full py-28 flex flex-col items-center">
599-
<h3 className="text-center pb-3 text-gray-500 dark:text-gray-400">No Access Tokens</h3>
600-
<p className="text-center pb-6 text-gray-500 text-base w-96">
601-
Generate an access token for applications that need access to the Gitpod API.{" "}
602-
</p>
603-
<Link to={settingsPathPersonalAccessTokenCreate}>
604-
<button>New Access Token</button>
605-
</Link>
606-
</div>
609+
{loading ? (
610+
<SpinnerLoader content="loading access token list" />
607611
) : (
608612
<>
609-
<div className="px-3 py-3 flex justify-between space-x-2 text-sm text-gray-400 mb-2 bg-gray-100 dark:bg-gray-800 rounded-xl">
610-
<h2 className="w-4/12">Token Name</h2>
611-
<h2 className="w-4/12">Permissions</h2>
612-
<h2 className="w-3/12">Expires</h2>
613-
<div className="w-1/12"></div>
614-
</div>
615-
{tokens.map((t: PersonalAccessToken) => (
616-
<TokenEntry
617-
key={t.id}
618-
token={t}
619-
menuEntries={[
620-
{
621-
title: "Edit",
622-
link: `${settingsPathPersonalAccessTokenEdit}/${t.id}`,
623-
},
624-
{
625-
title: "Regenerate",
626-
href: "",
627-
customFontStyle:
628-
"text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300",
629-
onClick: () => setModalData({ token: t, action: TokenAction.Regerenrate }),
630-
},
631-
{
632-
title: "Delete",
633-
href: "",
634-
customFontStyle:
635-
"text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300",
636-
onClick: () => setModalData({ token: t, action: TokenAction.Delete }),
637-
},
638-
]}
639-
/>
640-
))}
613+
{tokens.length === 0 ? (
614+
<div className="bg-gray-100 dark:bg-gray-800 rounded-xl w-full py-28 flex flex-col items-center">
615+
<h3 className="text-center pb-3 text-gray-500 dark:text-gray-400">
616+
No Access Tokens (PAT)
617+
</h3>
618+
<p className="text-center pb-6 text-gray-500 text-base w-96">
619+
Generate a access token (PAT) for applications that need access to the Gitpod
620+
API.{" "}
621+
</p>
622+
<Link to={settingsPathPersonalAccessTokenCreate}>
623+
<button>New Access Token</button>
624+
</Link>
625+
</div>
626+
) : (
627+
<>
628+
<div className="px-3 py-3 flex justify-between space-x-2 text-sm text-gray-400 mb-2 bg-gray-100 dark:bg-gray-800 rounded-xl">
629+
<h2 className="w-4/12">Token Name</h2>
630+
<h2 className="w-4/12">Permissions</h2>
631+
<h2 className="w-3/12">Expires</h2>
632+
<div className="w-1/12"></div>
633+
</div>
634+
{tokens.map((t: PersonalAccessToken) => (
635+
<TokenEntry
636+
key={t.id}
637+
token={t}
638+
menuEntries={[
639+
{
640+
title: "Edit",
641+
link: `${settingsPathPersonalAccessTokenEdit}/${t.id}`,
642+
},
643+
{
644+
title: "Regenerate",
645+
href: "",
646+
customFontStyle:
647+
"text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300",
648+
onClick: () => setModalData({ token: t, action: TokenAction.Regerenrate }),
649+
},
650+
{
651+
title: "Delete",
652+
href: "",
653+
customFontStyle:
654+
"text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300",
655+
onClick: () => setModalData({ token: t, action: TokenAction.Delete }),
656+
},
657+
]}
658+
/>
659+
))}
660+
</>
661+
)}
641662
</>
642663
)}
664+
643665
{modalData?.action === TokenAction.Delete && (
644666
<ShowTokenModal
645667
token={modalData.token}

0 commit comments

Comments
 (0)