Skip to content

Commit edf6b31

Browse files
committed
Prepare team service accounts component for moving to use Suspense
As part of the upcoming change, clientLoader will start returning a Promise rather than the resolved value. For Suspense to work, the body of the component function can't depend on the resolved values, so move such parts into subcomponents.
1 parent 3616204 commit edf6b31

File tree

2 files changed

+90
-68
lines changed

2 files changed

+90
-68
lines changed

apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx

Lines changed: 21 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,19 @@ import {
77
NewAlert,
88
NewButton,
99
Modal,
10-
NewTable,
1110
NewIcon,
1211
NewTextInput,
13-
Heading,
1412
CodeBox,
1513
} from "@thunderstore/cyberstorm";
16-
import { TableSort } from "@thunderstore/cyberstorm/src/newComponents/Table/Table";
1714
import {
18-
type RequestConfig,
1915
teamAddServiceAccount,
2016
type TeamServiceAccountAddRequestData,
2117
} from "@thunderstore/thunderstore-api";
2218

2319
import { type OutletContextShape } from "app/root";
2420
import { makeTeamSettingsTabLoader } from "cyberstorm/utils/dapperClientLoaders";
2521
import { useStrongForm } from "cyberstorm/utils/StrongForm/useStrongForm";
26-
import { ServiceAccountRemoveModal } from "./ServiceAccountRemoveModal";
22+
import { ServiceAccountsTable } from "./ServiceAccountsTable";
2723
import "./ServiceAccounts.css";
2824

2925
export const clientLoader = makeTeamSettingsTabLoader(
@@ -36,81 +32,30 @@ export function HydrateFallback() {
3632
return <div style={{ padding: "32px" }}>Loading...</div>;
3733
}
3834

39-
const serviceAccountColumns = [
40-
{ value: "Nickname", disableSort: false },
41-
{ value: "Last Used", disableSort: false },
42-
{ value: "Actions", disableSort: true },
43-
];
44-
4535
export default function ServiceAccounts() {
4636
const { teamName, serviceAccounts } = useLoaderData<typeof clientLoader>();
47-
const outletContext = useOutletContext() as OutletContextShape;
48-
4937
const revalidator = useRevalidator();
5038

51-
const currentUserTeam = outletContext.currentUser?.teams_full?.find(
52-
(team) => team.name === teamName
53-
);
54-
const isOwner = currentUserTeam?.role === "owner";
55-
5639
async function serviceAccountRevalidate() {
5740
revalidator.revalidate();
5841
}
5942

60-
const tableData = serviceAccounts.map((serviceAccount) => {
61-
return [
62-
{
63-
value: (
64-
<p className="team-service-accounts__nickname">
65-
{serviceAccount.name}
66-
</p>
67-
),
68-
sortValue: serviceAccount.name,
69-
},
70-
{
71-
value: (
72-
<p className="team-service-accounts__last-used">
73-
{serviceAccount.last_used ?? "Never"}
74-
</p>
75-
),
76-
sortValue: serviceAccount.last_used ?? "0",
77-
},
78-
{
79-
value: (
80-
<ServiceAccountRemoveModal
81-
key={serviceAccount.identifier}
82-
serviceAccount={serviceAccount}
83-
teamName={teamName}
84-
outletContext={outletContext}
85-
revalidate={serviceAccountRevalidate}
86-
/>
87-
),
88-
sortValue: 0,
89-
},
90-
];
91-
});
92-
9343
return (
9444
<div className="settings-items">
9545
<div className="settings-items__item">
9646
<div className="settings-items__meta">
9747
<p className="settings-items__title">Service accounts</p>
9848
<p className="settings-items__description">Your loyal servants</p>
99-
{isOwner && (
100-
<AddServiceAccountForm
101-
teamName={teamName}
102-
config={outletContext.requestConfig}
103-
serviceAccountRevalidate={serviceAccountRevalidate}
104-
/>
105-
)}
49+
<AddServiceAccountForm
50+
teamName={teamName}
51+
serviceAccountRevalidate={serviceAccountRevalidate}
52+
/>
10653
</div>
10754
<div className="settings-items__content">
108-
<NewTable
109-
titleRowContent={<Heading csLevel="3">Service Accounts</Heading>}
110-
headers={serviceAccountColumns}
111-
rows={tableData}
112-
sortByHeader={1}
113-
sortDirection={TableSort.ASC}
55+
<ServiceAccountsTable
56+
serviceAccounts={serviceAccounts}
57+
serviceAccountRevalidate={serviceAccountRevalidate}
58+
teamName={teamName}
11459
/>
11560
</div>
11661
</div>
@@ -120,16 +65,24 @@ export default function ServiceAccounts() {
12065

12166
function AddServiceAccountForm(props: {
12267
teamName: string;
123-
config: () => RequestConfig;
124-
serviceAccountRevalidate?: () => void;
68+
serviceAccountRevalidate: () => void;
12569
}) {
70+
const outletContext = useOutletContext() as OutletContextShape;
12671
const [open, setOpen] = useState(false);
12772
const [serviceAccountAdded, setServiceAccountAdded] = useState(false);
12873
const [addedServiceAccountToken, setAddedServiceAccountToken] = useState("");
12974
const [addedServiceAccountNickname, setAddedServiceAccountNickname] =
13075
useState("");
13176
const [error, setError] = useState<string | null>(null);
13277

78+
const currentUserTeam = outletContext.currentUser?.teams_full?.find(
79+
(team) => team.name === props.teamName
80+
);
81+
82+
if (currentUserTeam?.role !== "owner") {
83+
return null;
84+
}
85+
13386
function onSuccess(
13487
result: Awaited<ReturnType<typeof teamAddServiceAccount>>
13588
) {
@@ -161,7 +114,7 @@ function AddServiceAccountForm(props: {
161114

162115
async function submitor(data: typeof formInputs): Promise<SubmitorOutput> {
163116
return await teamAddServiceAccount({
164-
config: props.config,
117+
config: outletContext.requestConfig,
165118
params: { team_name: props.teamName },
166119
queryParams: {},
167120
data: { nickname: data.nickname.trim() },
@@ -188,7 +141,7 @@ function AddServiceAccountForm(props: {
188141
// Refresh the service accounts list to show the newly created account
189142
// TODO: When API returns identifier in response, we can append the new
190143
// service account to the list instead of refreshing from backend
191-
props.serviceAccountRevalidate?.();
144+
props.serviceAccountRevalidate();
192145
},
193146
onSubmitError: (error) => {
194147
const message = `Error occurred: ${error.message || "Unknown error"}`;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { useOutletContext } from "react-router";
2+
3+
import { NewTable, Heading } from "@thunderstore/cyberstorm";
4+
import { TableSort } from "@thunderstore/cyberstorm/src/newComponents/Table/Table";
5+
import { type TeamServiceAccount } from "@thunderstore/thunderstore-api";
6+
7+
import { type OutletContextShape } from "app/root";
8+
import { ServiceAccountRemoveModal } from "./ServiceAccountRemoveModal";
9+
import "./ServiceAccounts.css";
10+
11+
const serviceAccountColumns = [
12+
{ value: "Nickname", disableSort: false },
13+
{ value: "Last Used", disableSort: false },
14+
{ value: "Actions", disableSort: true },
15+
];
16+
17+
export function ServiceAccountsTable(props: {
18+
serviceAccounts: TeamServiceAccount[];
19+
teamName: string;
20+
serviceAccountRevalidate: () => Promise<void>;
21+
}) {
22+
const { serviceAccounts, serviceAccountRevalidate, teamName } = props;
23+
const outletContext = useOutletContext() as OutletContextShape;
24+
25+
const tableData = serviceAccounts.map((serviceAccount) => {
26+
return [
27+
{
28+
value: (
29+
<p className="team-service-accounts__nickname">
30+
{serviceAccount.name}
31+
</p>
32+
),
33+
sortValue: serviceAccount.name,
34+
},
35+
{
36+
value: (
37+
<p className="team-service-accounts__last-used">
38+
{serviceAccount.last_used ?? "Never"}
39+
</p>
40+
),
41+
sortValue: serviceAccount.last_used ?? "0",
42+
},
43+
{
44+
value: (
45+
<ServiceAccountRemoveModal
46+
key={serviceAccount.identifier}
47+
serviceAccount={serviceAccount}
48+
teamName={teamName}
49+
revalidate={serviceAccountRevalidate}
50+
outletContext={outletContext}
51+
/>
52+
),
53+
sortValue: 0,
54+
},
55+
];
56+
});
57+
58+
return (
59+
<NewTable
60+
titleRowContent={<Heading csLevel="3">Service Accounts</Heading>}
61+
headers={serviceAccountColumns}
62+
rows={tableData}
63+
sortByHeader={1}
64+
sortDirection={TableSort.ASC}
65+
/>
66+
);
67+
}
68+
69+
ServiceAccountsTable.displayName = "ServiceAccountsTable";

0 commit comments

Comments
 (0)