From 4452ad10b9f6be0fd26717d7b174eba16f10a370 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Thu, 27 Jun 2024 22:00:17 +0300 Subject: [PATCH] Add team settings pages --- apps/cyberstorm-remix/app/root.tsx | 1 - .../app/settings/teams/team/Tabs.module.css | 56 ++++++ .../teams/team/tabs/Members/Members.tsx | 100 +++++++++++ .../team/tabs/Members/TeamMemberList.tsx | 118 +++++++++++++ .../team/tabs/Members/TeamMembers.module.css | 57 +++++++ .../teams/team/tabs/Profile/Profile.tsx | 63 +++++++ .../ServiceAccountList.module.css | 20 +++ .../ServiceAccountList/ServiceAccountList.tsx | 58 +++++++ .../ServiceAccountListItem.module.css | 31 ++++ .../ServiceAccountListItem.tsx | 30 ++++ .../tabs/ServiceAccounts/ServiceAccounts.tsx | 112 ++++++++++++ .../TeamServiceAccounts.module.css | 37 ++++ .../teams/team/tabs/Settings/Settings.tsx | 150 ++++++++++++++++ .../Settings/TeamLeaveAndDisband.module.css | 19 +++ .../app/settings/teams/team/teamSettings.tsx | 161 ++++++++++++++++++ .../teams/team/teamSettingsLayout.module.css | 12 ++ .../cyberstorm/utils/LinkLibrary.tsx | 6 +- apps/cyberstorm-remix/vite.config.ts | 18 ++ .../actions/TeamMemberChangeRoleAction.tsx | 8 +- .../src/forms/AddServiceAccountForm.tsx | 6 +- .../src/forms/AddTeamMemberForm.tsx | 8 +- .../src/forms/DisbandTeamForm.tsx | 6 +- .../src/forms/LeaveTeamForm.tsx | 13 +- .../src/forms/RemoveTeamMemberForm.tsx | 6 +- .../src/forms/TeamDetailsEdit.tsx | 22 ++- .../src/components/Dialog/Dialog.tsx | 5 +- .../TeamSettings/TeamMembers/TeamMembers.tsx | 9 +- .../src/components/Links/LinkingProvider.tsx | 13 +- .../cyberstorm/src/components/Links/Links.tsx | 3 + packages/dapper-ts/src/methods/team.ts | 2 +- .../src/fetch/teamAddMember.ts | 4 +- .../src/fetch/teamAddServiceAccount.ts | 2 +- .../src/fetch/teamEditMember.ts | 2 +- .../src/fetch/teamRemoveMember.ts | 4 +- .../src/fetch/teamServiceAccountRemove.ts | 2 +- packages/ts-api-react-forms/src/schema.ts | 4 +- 36 files changed, 1115 insertions(+), 53 deletions(-) create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/Tabs.module.css create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/Members.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMemberList.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMembers.module.css create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/Profile/Profile.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.module.css create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.module.css create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/TeamServiceAccounts.module.css create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/Settings.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/TeamLeaveAndDisband.module.css create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx create mode 100644 apps/cyberstorm-remix/app/settings/teams/team/teamSettingsLayout.module.css diff --git a/apps/cyberstorm-remix/app/root.tsx b/apps/cyberstorm-remix/app/root.tsx index 6da9eef07..74019e9b6 100644 --- a/apps/cyberstorm-remix/app/root.tsx +++ b/apps/cyberstorm-remix/app/root.tsx @@ -119,7 +119,6 @@ function Root() { envStuff: { ENV: publicEnvVariables; }; - sessionId: string | null; currentUser: CurrentUser; } = JSON.parse(JSON.stringify(loaderOutput)); diff --git a/apps/cyberstorm-remix/app/settings/teams/team/Tabs.module.css b/apps/cyberstorm-remix/app/settings/teams/team/Tabs.module.css new file mode 100644 index 000000000..5e4036fe1 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/Tabs.module.css @@ -0,0 +1,56 @@ +.root { + display: flex; + flex-direction: column; + flex-grow: 1; + gap: var(--space--32); +} + +.buttons { + display: flex; + border-bottom: 3px solid var(--color-purple--5); +} + +.button { + display: flex; + flex: none; + flex-direction: row; + gap: var(--space--12); + align-items: center; + justify-content: center; + margin-bottom: -3px; + + padding: var(--space--12) var(--space--16); + border-bottom: 3px solid var(--color-purple--5); + color: var(--tab-color); + background-color: transparent; + + --tab-color: var(--color-text--default); +} + +.button.active { + border-color: var(--tab-color); + + --tab-color: var(--color-green--5); +} + +.button:disabled { + --tab-color: var(--color-border--highlight); +} + +.icon { + color: var(--color-border--highlight); +} + +.button.active .icon { + color: var(--tab-color); +} + +.button:not(.active, :disabled):hover > .icon { + color: var(--color-text--tertiary); +} + +.label { + font-weight: var(--font-weight-bold); + font-size: var(--font-size--l); + line-height: 1.3; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/Members.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/Members.tsx new file mode 100644 index 000000000..501dc5a4e --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/Members.tsx @@ -0,0 +1,100 @@ +import { TeamMemberList } from "./TeamMemberList"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlus } from "@fortawesome/pro-solid-svg-icons"; +import { AddTeamMemberForm } from "@thunderstore/cyberstorm-forms"; +import { SettingItem, Dialog, Button } from "@thunderstore/cyberstorm"; +import { LoaderFunctionArgs } from "@remix-run/node"; +import { useLoaderData, useRevalidator } from "@remix-run/react"; +import { ApiError } from "@thunderstore/thunderstore-api"; +import { getDapper } from "cyberstorm/dapper/sessionUtils"; +import { membersSchema } from "@thunderstore/dapper-ts/src/methods/team"; + +// REMIX TODO: Since the server loader has to exist for whatever reason? +// We have to return empty list for the members +// as permissions are needed for retrieving that data +// which the server doesn't have +// Fix this to not be stupid +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + try { + return { + teamName: params.namespaceId, + members: [] as typeof membersSchema._type, + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +// REMIX TODO: Add check for "user has permission to see this page" +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + const dapper = await getDapper(true); + const currentUser = await dapper.getCurrentUser(); + if (!currentUser.username) { + throw new Response("Not logged in.", { status: 401 }); + } + try { + const dapper = await getDapper(true); + return { + teamName: params.namespaceId, + members: await dapper.getTeamMembers(params.namespaceId), + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +clientLoader.hydrate = true; + +export default function Page() { + const { teamName, members } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + const revalidator = useRevalidator(); + + async function addTeamMemberRevalidate() { + revalidator.revalidate(); + } + + return ( + + Add Member + + + + + } + > + + + } + content={} + /> + ); +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMemberList.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMemberList.tsx new file mode 100644 index 000000000..9b7150641 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMemberList.tsx @@ -0,0 +1,118 @@ +"use client"; +import { faTrashCan } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { TeamMember } from "@thunderstore/dapper/types"; + +import styles from "./TeamMembers.module.css"; +import { + RemoveTeamMemberForm, + TeamMemberChangeRoleAction, +} from "@thunderstore/cyberstorm-forms"; +import { useState } from "react"; +import { + CyberstormLink, + Dialog, + Button, + Avatar, + Sort, + Table, +} from "@thunderstore/cyberstorm"; +import { useRevalidator } from "@remix-run/react"; + +const teamMemberColumns = [ + { value: "User", disableSort: false }, + { value: "Role", disableSort: false }, + { value: "Actions", disableSort: true }, +]; + +interface Props { + members: TeamMember[]; + teamName: string; +} + +export function TeamMemberList(props: Props) { + const { members } = props; + + const revalidator = useRevalidator(); + + // REMIX TODO: Move current user to stand-alone loader and revalidate only currentUser + async function teamMemberRevalidate() { + revalidator.revalidate(); + } + + const tableData = members.map((member, index) => { + return [ + { + value: ( + +
+ + {member.username} +
+
+ ), + sortValue: member.username, + }, + { + value: ( +
+ +
+ ), + sortValue: member.role, + }, + { + value: ( + + + + + Kick + + } + > + + + ), + sortValue: 0, + }, + ]; + }); + + return ( + + ); +} + +TeamMemberList.displayName = "TeamMemberList"; diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMembers.module.css b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMembers.module.css new file mode 100644 index 000000000..8518b558c --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/TeamMembers.module.css @@ -0,0 +1,57 @@ +.dialogContent { + display: flex; + flex-direction: column; + gap: var(--gap--32); +} + +.dialogTeamName { + color: var(--color-highlight); +} + +.dialogInput { + display: flex; + flex-direction: row; + gap: var(--gap--16); + align-items: flex-end; + width: 100%; +} + +.textInput { + flex-grow: 1; +} + +.roleSelect { + width: 11em; +} + +.roleSelect > div > button { + width: 100%; +} + +.userInfo { + display: flex; + gap: var(--gap--16); + align-items: center; +} + +.userInfoName { + font-weight: var(--font-weight-bold); + font-size: var(--font-size--m); + line-height: var(--line-height--m); +} + +.description { + color: var(--color-text--secondary); + font-weight: var(--font-weight-medium); + font-size: var(--font-size--l); + line-height: var(--line-height--l); +} + +.kickDescriptionUserName { + color: var(--color-highlight); +} + +.dialogFooter { + display: flex; + flex-direction: row-reverse; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/Profile/Profile.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Profile/Profile.tsx new file mode 100644 index 000000000..cf8ee1500 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Profile/Profile.tsx @@ -0,0 +1,63 @@ +import { LoaderFunctionArgs } from "@remix-run/node"; +import { useLoaderData, useRevalidator } from "@remix-run/react"; +import { TeamDetailsEdit } from "@thunderstore/cyberstorm-forms"; +import { ApiError } from "@thunderstore/thunderstore-api"; +import { getDapper } from "cyberstorm/dapper/sessionUtils"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + try { + const dapper = await getDapper(); + return { + team: await dapper.getTeamDetails(params.namespaceId), + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +// REMIX TODO: Add check for "user has permission to see this page" +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + const dapper = await getDapper(true); + const currentUser = await dapper.getCurrentUser(); + if (!currentUser.username) { + throw new Response("Not logged in.", { status: 401 }); + } + try { + const dapper = await getDapper(true); + return { + team: await dapper.getTeamDetails(params.namespaceId), + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +clientLoader.hydrate = true; + +export default function Profile() { + const { team } = useLoaderData(); + + const revalidator = useRevalidator(); + + async function teamProfileRevalidate() { + revalidator.revalidate(); + } + + return ; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.module.css b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.module.css new file mode 100644 index 000000000..14480560d --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.module.css @@ -0,0 +1,20 @@ +.root { + display: flex; + flex-direction: column; + gap: var(--gap--8); +} + +.removeDescriptionAccountName { + color: var(--color-highlight); +} + +.content { + display: flex; + flex-direction: column; + gap: var(--gap--32); +} + +.removeServiceAccountFooter { + display: flex; + flex-direction: row-reverse; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.tsx new file mode 100644 index 000000000..995f2c884 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountList.tsx @@ -0,0 +1,58 @@ +"use client"; +import { ServiceAccount } from "@thunderstore/dapper/types"; +import { RemoveServiceAccountForm } from "@thunderstore/cyberstorm-forms"; +import { useState } from "react"; +import { Dialog, Button, Table } from "@thunderstore/cyberstorm"; + +const serviceAccountColumns = [ + { value: "Nickname", disableSort: false }, + { value: "Last Used", disableSort: false }, + { value: "Actions", disableSort: true }, +]; + +interface Props { + teamName: string; + serviceAccounts: ServiceAccount[]; +} + +export function ServiceAccountList(props: Props) { + const { serviceAccounts } = props; + + const [dialogOpen, setOpenDialog] = useState(false); + const tableData = serviceAccounts.map((serviceAccount, index) => { + return [ + { value: serviceAccount.name, sortValue: serviceAccount.name }, + { + value: serviceAccount.last_used ?? "Never", + sortValue: serviceAccount.last_used ?? "Never", + }, + { + value: ( + + Remove + + } + > + + + ), + sortValue: 0, + }, + ]; + }); + + return
; +} + +ServiceAccountList.displayName = "ServiceAccountList"; diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.module.css b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.module.css new file mode 100644 index 000000000..43ccd9d46 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.module.css @@ -0,0 +1,31 @@ +.root { + display: flex; + flex-direction: row; + gap: var(--gap--16); + padding: var(--space--16) var(--space--24); + border-radius: var(--border-radius--8); + background-color: var(--color-surface--4); +} + +.name { + display: flex; + flex: 2; + flex-direction: row; + gap: var(--gap--16); + align-items: center; + font: var(--font-heading); +} + +.details { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; +} + +.action { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.tsx new file mode 100644 index 000000000..c3e999946 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccountList/ServiceAccountListItem.tsx @@ -0,0 +1,30 @@ +import styles from "./ServiceAccountListItem.module.css"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTrashCan } from "@fortawesome/free-solid-svg-icons"; +import { Button } from "@thunderstore/cyberstorm"; + +export interface ServiceAccountListItemProps { + serviceAccountName?: string; + lastUsed?: string; +} + +export function ServiceAccountListItem(props: ServiceAccountListItemProps) { + const { serviceAccountName = "", lastUsed = "" } = props; + + return ( +
+
{serviceAccountName}
+
{lastUsed}
+
+ + + + + Remove + +
+
+ ); +} + +ServiceAccountListItem.displayName = "ServiceAccountListItem"; diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx new file mode 100644 index 000000000..bc288fb97 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx @@ -0,0 +1,112 @@ +import { SettingItem, Dialog, Button } from "@thunderstore/cyberstorm"; +import { AddServiceAccountForm } from "@thunderstore/cyberstorm-forms"; +import { ServiceAccountList } from "./ServiceAccountList/ServiceAccountList"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlus } from "@fortawesome/pro-solid-svg-icons"; +import { LoaderFunctionArgs } from "@remix-run/node"; +import { useLoaderData, useRevalidator } from "@remix-run/react"; +import { serviceAccountSchema } from "@thunderstore/dapper-ts/src/methods/team"; +import { ApiError } from "@thunderstore/thunderstore-api"; +import { getDapper } from "cyberstorm/dapper/sessionUtils"; + +// REMIX TODO: Since the server loader has to exist for whatever reason? +// We have to return empty list for the members +// as permissions are needed for retrieving that data +// which the server doesn't have +// Fix this to not be stupid +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + try { + return { + teamName: params.namespaceId, + serviceAccounts: [] as typeof serviceAccountSchema._type, + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +// REMIX TODO: Add check for "user has permission to see this page" +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + const dapper = await getDapper(true); + const currentUser = await dapper.getCurrentUser(); + if (!currentUser.username) { + throw new Response("Not logged in.", { status: 401 }); + } + try { + const dapper = await getDapper(true); + return { + teamName: params.namespaceId, + serviceAccounts: await dapper.getTeamServiceAccounts( + params.namespaceId + ), + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +clientLoader.hydrate = true; + +export default function ServiceAccounts() { + const { teamName, serviceAccounts } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + const revalidator = useRevalidator(); + + // REMIX TODO: Move current user to stand-alone loader and revalidate only currentUser + async function addServiceAccountRevalidate() { + revalidator.revalidate(); + } + + return ( +
+ + + Add Service Account + + + + + } + > + + +
+ } + content={ + + } + /> + + ); +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/TeamServiceAccounts.module.css b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/TeamServiceAccounts.module.css new file mode 100644 index 000000000..a9349990a --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/TeamServiceAccounts.module.css @@ -0,0 +1,37 @@ +.content { + display: flex; + flex-direction: column; + gap: var(--space--16); +} + +.dialogContent { + display: flex; + flex-direction: column; + gap: var(--gap--32); +} + +.dialogTeamName, +.dialogAddedServiceAccountName { + color: var(--color-highlight); +} + +.description { + color: var(--color-text--secondary); + font-weight: var(--font-weight-medium); + font-size: var(--font-size--l); + line-height: var(--line-height--l); +} + +.token { + display: flex; + gap: var(--gap--8); +} + +.tokenText { + width: 100%; +} + +.addServiceAccountDialogFooter { + display: flex; + flex-direction: row-reverse; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/Settings.tsx b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/Settings.tsx new file mode 100644 index 000000000..75290d401 --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/Settings.tsx @@ -0,0 +1,150 @@ +import styles from "./TeamLeaveAndDisband.module.css"; +import { SettingItem, Alert, Dialog, Button } from "@thunderstore/cyberstorm"; +import { LeaveTeamForm, DisbandTeamForm } from "@thunderstore/cyberstorm-forms"; +import { useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faOctagonExclamation, + faTrashCan, +} from "@fortawesome/pro-solid-svg-icons"; +import { currentUserSchema } from "@thunderstore/dapper-ts"; +import { getDapper } from "cyberstorm/dapper/sessionUtils"; +import { LoaderFunctionArgs } from "@remix-run/node"; +import { useLoaderData, useRevalidator } from "@remix-run/react"; +import { ApiError } from "@thunderstore/thunderstore-api"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + try { + const dapper = await getDapper(); + const currentUser = await dapper.getCurrentUser(); + return { + teamName: params.namespaceId, + currentUser: currentUser as typeof currentUserSchema._type, + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +// REMIX TODO: Add check for "user has permission to see this page" +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + const dapper = await getDapper(true); + const currentUser = await dapper.getCurrentUser(); + if (!currentUser.username) { + throw new Response("Not logged in.", { status: 401 }); + } + try { + return { + teamName: params.namespaceId, + currentUser: currentUser as typeof currentUserSchema._type, + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +clientLoader.hydrate = true; + +// REMIX TODO: Make sure user is redirected of this page, if the user is not logged in +export default function Settings() { + const { teamName, currentUser } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + return ( +
+ + } + content={ + "You cannot currently leave this team as you are it's last owner." + } + variant="danger" + /> +

+ If you are the owner of the team, you can only leave if the team + has another owner assigned. +

+
+ + + + + Leave team + + } + > + + +
+
+ } + /> +
+ + } + content={ + "You cannot currently disband this team as it has packages." + } + variant="danger" + /> +

+ You are about to disband the team {teamName}. +

+

+ Be aware you can currently only disband teams with no packages. If + you need to archive a team with existing pages, contact + Mythic#0001 on the Thunderstore Discord. +

+
+ + + + + Disband team + + } + > + + +
+
+ } + /> + + ); +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/TeamLeaveAndDisband.module.css b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/TeamLeaveAndDisband.module.css new file mode 100644 index 000000000..3ab19e3aa --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/TeamLeaveAndDisband.module.css @@ -0,0 +1,19 @@ +.content { + display: flex; + flex-direction: column; + gap: var(--space--16); +} + +.description { + font-size: var(--font-size--m); + line-height: normal; +} + +.separator { + margin: var(--space--32) 0; + border-bottom: var(--border-width--px) solid var(--color-surface--5); +} + +.disbandVerificationInput { + display: flex; +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx b/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx new file mode 100644 index 000000000..e581dc6ed --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx @@ -0,0 +1,161 @@ +import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; +import { Outlet, useLoaderData, useLocation } from "@remix-run/react"; +import { + BreadCrumbs, + CyberstormLink, + Icon, + PageHeader, +} from "@thunderstore/cyberstorm"; +import tabsStyles from "./Tabs.module.css"; +import styles from "./teamSettingsLayout.module.css"; +import rootStyles from "../../../RootLayout.module.css"; +import { getDapper } from "cyberstorm/dapper/sessionUtils"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ApiError } from "@thunderstore/thunderstore-api"; +import { + faFileLines, + faFilePlus, + faCodeBranch, + faBook, +} from "@fortawesome/pro-solid-svg-icons"; +import { classnames } from "@thunderstore/cyberstorm/src/utils/utils"; + +export const meta: MetaFunction = ({ data }) => { + return [ + { title: `${data?.team.name} settings` }, + { name: "description", content: `${data?.team.name} settings` }, + ]; +}; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + try { + const dapper = await getDapper(); + return { + team: await dapper.getTeamDetails(params.namespaceId), + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +// REMIX TODO: Add check for "user has permission to see this page" +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId) { + try { + const dapper = await getDapper(true); + return { + team: await dapper.getTeamDetails(params.namespaceId), + }; + } catch (error) { + if (error instanceof ApiError) { + throw new Response("Team not found", { status: 404 }); + } else { + // REMIX TODO: Add sentry + throw error; + } + } + } + throw new Response("Team not found", { status: 404 }); +} + +clientLoader.hydrate = true; + +export default function Community() { + const { team } = useLoaderData(); + const location = useLocation(); + + const currentTab = location.pathname.endsWith("/settings") + ? "settings" + : location.pathname.endsWith("/members") + ? "members" + : location.pathname.endsWith("/service-accounts") + ? "service-accounts" + : "profile"; + + return ( + <> + + Teams + {team.name} + +
+
+ +
+
+
+
+
+
+ + + + + Profile + + + + + + Members + + + + + + Service Accounts + + + + + + Settings + +
+ +
+
+
+ + ); +} diff --git a/apps/cyberstorm-remix/app/settings/teams/team/teamSettingsLayout.module.css b/apps/cyberstorm-remix/app/settings/teams/team/teamSettingsLayout.module.css new file mode 100644 index 000000000..3c901c0db --- /dev/null +++ b/apps/cyberstorm-remix/app/settings/teams/team/teamSettingsLayout.module.css @@ -0,0 +1,12 @@ +.header { + display: flex; + flex-direction: row; + gap: var(--gap--32); + justify-content: space-between; +} + +.teamContainer { + display: flex; + flex-direction: row; + gap: 2rem; +} diff --git a/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx b/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx index 6732f4f57..100dcf0d9 100644 --- a/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx +++ b/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx @@ -83,7 +83,11 @@ const library: LinkLibrary = { Settings: (p) => Link({ ...p, url: `/settings/` }), Team: (p) => Link({ ...p, url: `/c/${p.community}/p/${p.team}/` }), Teams: (p) => Link({ ...p, url: `/teams/` }), - TeamSettings: (p) => Link({ ...p, url: `/teams/${p.team}/profile` }), + TeamSettings: (p) => Link({ ...p, url: `/teams/${p.team}` }), + TeamSettingsMembers: (p) => Link({ ...p, url: `/teams/${p.team}/members` }), + TeamSettingsServiceAccounts: (p) => + Link({ ...p, url: `/teams/${p.team}/service-accounts` }), + TeamSettingsSettings: (p) => Link({ ...p, url: `/teams/${p.team}/settings` }), TermsOfService: (p) => Link({ ...p, url: "/terms-of-service/" }), User: (p) => Link({ ...p, url: `/u/${p.user}/` }), }; diff --git a/apps/cyberstorm-remix/vite.config.ts b/apps/cyberstorm-remix/vite.config.ts index b759795e9..322ce8666 100644 --- a/apps/cyberstorm-remix/vite.config.ts +++ b/apps/cyberstorm-remix/vite.config.ts @@ -41,6 +41,24 @@ export default defineConfig({ "p/dependants/Dependants.tsx" ); route("/teams", "settings/teams/Teams.tsx"); + route( + "/teams/:namespaceId", + "settings/teams/team/teamSettings.tsx", + () => { + route("", "settings/teams/team/tabs/Profile/Profile.tsx", { + index: true, + }); + route("members", "settings/teams/team/tabs/Members/Members.tsx"); + route( + "service-accounts", + "settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx" + ); + route( + "settings", + "settings/teams/team/tabs/Settings/Settings.tsx" + ); + } + ); }); }, }), diff --git a/packages/cyberstorm-forms/src/actions/TeamMemberChangeRoleAction.tsx b/packages/cyberstorm-forms/src/actions/TeamMemberChangeRoleAction.tsx index 20f83cc4b..9ea4ec6a9 100644 --- a/packages/cyberstorm-forms/src/actions/TeamMemberChangeRoleAction.tsx +++ b/packages/cyberstorm-forms/src/actions/TeamMemberChangeRoleAction.tsx @@ -10,6 +10,7 @@ export function TeamMemberChangeRoleAction(props: { teamName: string; userName: string; currentRole: "member" | "owner"; + updateTrigger: () => Promise; }) { const { onSubmitSuccess, onSubmitError } = useFormToaster({ successMessage: `Changed the role of ${props.userName} to TODO`, @@ -24,12 +25,15 @@ export function TeamMemberChangeRoleAction(props: { schema: teamEditMemberFormSchema, meta: { teamIdentifier: props.teamName }, endpoint: teamEditMember, - onSubmitSuccess: onSubmitSuccess, + onSubmitSuccess: () => { + onSubmitSuccess(); + props.updateTrigger(); + }, onSubmitError: onSubmitError, }); function onChange(value: string) { - onSubmit({ user: props.userName, role: value }); + onSubmit({ username: props.userName, role: value }); } return ( diff --git a/packages/cyberstorm-forms/src/forms/AddServiceAccountForm.tsx b/packages/cyberstorm-forms/src/forms/AddServiceAccountForm.tsx index 8d93156d2..ec0a12a84 100644 --- a/packages/cyberstorm-forms/src/forms/AddServiceAccountForm.tsx +++ b/packages/cyberstorm-forms/src/forms/AddServiceAccountForm.tsx @@ -32,7 +32,10 @@ function isServiceAccountSuccessResponse( ); } -export function AddServiceAccountForm(props: { teamName: string }) { +export function AddServiceAccountForm(props: { + teamName: string; + updateTrigger: () => void; +}) { const { onSubmitSuccess, onSubmitError } = useFormToaster({ successMessage: "Service account created", }); @@ -51,6 +54,7 @@ export function AddServiceAccountForm(props: { teamName: string }) { setServiceAccountAdded(true); setAddedServiceAccountToken(responseJson.service_account_token); setAddedServiceAccountNickname(responseJson.service_account_nickname); + props.updateTrigger(); onSubmitSuccess(); } else { onSubmitError(); diff --git a/packages/cyberstorm-forms/src/forms/AddTeamMemberForm.tsx b/packages/cyberstorm-forms/src/forms/AddTeamMemberForm.tsx index 46028708c..7b32c989b 100644 --- a/packages/cyberstorm-forms/src/forms/AddTeamMemberForm.tsx +++ b/packages/cyberstorm-forms/src/forms/AddTeamMemberForm.tsx @@ -14,7 +14,7 @@ import { import { FormSelect } from "../components/FormSelect"; export function AddTeamMemberForm(props: { - dialogOnChange: (v: boolean) => void; + updateTrigger?: () => void; teamName: string; }) { const { onSubmitSuccess, onSubmitError } = useFormToaster({ @@ -30,7 +30,9 @@ export function AddTeamMemberForm(props: { { onSubmitSuccess(); - props.dialogOnChange(false); + if (props.updateTrigger) { + props.updateTrigger(); + } }} onSubmitError={onSubmitError} schema={teamAddMemberFormSchema} @@ -47,7 +49,7 @@ export function AddTeamMemberForm(props: {
diff --git a/packages/cyberstorm-forms/src/forms/DisbandTeamForm.tsx b/packages/cyberstorm-forms/src/forms/DisbandTeamForm.tsx index ee19109a5..6a8a5d06c 100644 --- a/packages/cyberstorm-forms/src/forms/DisbandTeamForm.tsx +++ b/packages/cyberstorm-forms/src/forms/DisbandTeamForm.tsx @@ -14,10 +14,7 @@ import { import { CyberstormLink } from "@thunderstore/cyberstorm"; -export function DisbandTeamForm(props: { - dialogOnChange: (v: boolean) => void; - teamName: string; -}) { +export function DisbandTeamForm(props: { teamName: string }) { const { onSubmitSuccess, onSubmitError } = useFormToaster({ successMessage: `Team ${props.teamName} disbanded`, }); @@ -26,7 +23,6 @@ export function DisbandTeamForm(props: { { onSubmitSuccess(); - props.dialogOnChange(false); }} onSubmitError={onSubmitError} schema={teamDisbandFormSchema} diff --git a/packages/cyberstorm-forms/src/forms/LeaveTeamForm.tsx b/packages/cyberstorm-forms/src/forms/LeaveTeamForm.tsx index 94695382b..9868b5ac2 100644 --- a/packages/cyberstorm-forms/src/forms/LeaveTeamForm.tsx +++ b/packages/cyberstorm-forms/src/forms/LeaveTeamForm.tsx @@ -11,33 +11,28 @@ import { import { z } from "zod"; import { CyberstormLink } from "@thunderstore/cyberstorm"; -export function LeaveTeamForm(props: { - dialogOnChange: (v: boolean) => void; - userName: string; - teamName: string; -}) { +export function LeaveTeamForm(props: { username: string; teamName: string }) { const { onSubmitSuccess, onSubmitError } = useFormToaster({ - successMessage: `${props.userName} left team ${props.teamName}`, + successMessage: `${props.username} left team ${props.teamName}`, }); return ( { onSubmitSuccess(); - props.dialogOnChange(false); }} onSubmitError={onSubmitError} schema={z.object({})} endpoint={teamRemoveMember} formProps={{ className: styles.root }} - meta={{ teamIdentifier: props.teamName, user: props.userName }} + meta={{ teamIdentifier: props.teamName, username: props.username }} >
You are about to leave the team{" "} - {props.userName} + {props.username}
diff --git a/packages/cyberstorm-forms/src/forms/RemoveTeamMemberForm.tsx b/packages/cyberstorm-forms/src/forms/RemoveTeamMemberForm.tsx index e2e6c0215..ee35c7fd8 100644 --- a/packages/cyberstorm-forms/src/forms/RemoveTeamMemberForm.tsx +++ b/packages/cyberstorm-forms/src/forms/RemoveTeamMemberForm.tsx @@ -12,9 +12,9 @@ import { z } from "zod"; import { CyberstormLink } from "@thunderstore/cyberstorm"; export function RemoveTeamMemberForm(props: { - dialogOnChange: (v: boolean) => void; userName: string; teamName: string; + updateTrigger: () => Promise; }) { const { onSubmitSuccess, onSubmitError } = useFormToaster({ successMessage: `User ${"TODO"} removed from team ${"TODO"}`, @@ -24,13 +24,13 @@ export function RemoveTeamMemberForm(props: { { onSubmitSuccess(); - props.dialogOnChange(false); + props.updateTrigger(); }} onSubmitError={onSubmitError} schema={z.object({})} endpoint={teamRemoveMember} formProps={{ className: styles.root }} - meta={{ teamIdentifier: props.teamName, user: props.userName }} + meta={{ teamIdentifier: props.teamName, username: props.userName }} >
diff --git a/packages/cyberstorm-forms/src/forms/TeamDetailsEdit.tsx b/packages/cyberstorm-forms/src/forms/TeamDetailsEdit.tsx index 107e4fd98..85068bfb1 100644 --- a/packages/cyberstorm-forms/src/forms/TeamDetailsEdit.tsx +++ b/packages/cyberstorm-forms/src/forms/TeamDetailsEdit.tsx @@ -11,21 +11,27 @@ import { FormTextInput, useFormToaster, } from "@thunderstore/cyberstorm-forms"; -import { useDapper } from "@thunderstore/dapper"; -import { usePromise } from "@thunderstore/use-promise"; import { SettingItem } from "@thunderstore/cyberstorm/src/components/SettingItem/SettingItem"; +import { TeamDetails } from "@thunderstore/dapper/types"; -export function TeamDetailsEdit({ teamName }: { teamName: string }) { - const toaster = useFormToaster({ +export function TeamDetailsEdit({ + team, + updateTrigger, +}: { + team: TeamDetails; + updateTrigger: () => Promise; +}) { + const { onSubmitSuccess, onSubmitError } = useFormToaster({ successMessage: "Changes saved", }); - const dapper = useDapper(); - const team = usePromise(dapper.getTeamDetails, [teamName]); - return ( { + onSubmitSuccess(); + updateTrigger(); + }} + onSubmitError={onSubmitError} schema={teamDetailsEditFormSchema} meta={{ teamIdentifier: team.name }} endpoint={teamDetailsEdit} diff --git a/packages/cyberstorm/src/components/Dialog/Dialog.tsx b/packages/cyberstorm/src/components/Dialog/Dialog.tsx index dede1353c..038d8bbec 100644 --- a/packages/cyberstorm/src/components/Dialog/Dialog.tsx +++ b/packages/cyberstorm/src/components/Dialog/Dialog.tsx @@ -16,13 +16,16 @@ interface DialogProps extends PropsWithChildren { showHeaderBorder?: boolean; } +// TODO: This needs to be reworked to use Popover API /** * Cyberstorm Dialog Component */ export function Dialog(props: DialogProps) { const { children, + // eslint-disable-next-line @typescript-eslint/no-unused-vars open, + // eslint-disable-next-line @typescript-eslint/no-unused-vars onOpenChange, trigger, title = undefined, @@ -33,7 +36,7 @@ export function Dialog(props: DialogProps) { return (
- + {trigger} diff --git a/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamMembers/TeamMembers.tsx b/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamMembers/TeamMembers.tsx index 19431dfa1..e2bdfbe72 100644 --- a/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamMembers/TeamMembers.tsx +++ b/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamMembers/TeamMembers.tsx @@ -8,7 +8,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus } from "@fortawesome/pro-solid-svg-icons"; import { Dialog } from "../../../../.."; import { AddTeamMemberForm } from "@thunderstore/cyberstorm-forms"; -import { useState } from "react"; interface Props { teamName: string; @@ -19,7 +18,6 @@ export function TeamMembers(props: Props) { const dapper = useDapper(); const members = usePromise(dapper.getTeamMembers, [teamName]); - const [dialogOpen, setOpenDialog] = useState(false); return (
@@ -28,8 +26,6 @@ export function TeamMembers(props: Props) { description="Your best buddies" additionalLeftColumnContent={ @@ -40,10 +36,7 @@ export function TeamMembers(props: Props) { } > - + } content={} diff --git a/packages/cyberstorm/src/components/Links/LinkingProvider.tsx b/packages/cyberstorm/src/components/Links/LinkingProvider.tsx index e84b2795f..07c5e0388 100644 --- a/packages/cyberstorm/src/components/Links/LinkingProvider.tsx +++ b/packages/cyberstorm/src/components/Links/LinkingProvider.tsx @@ -102,8 +102,16 @@ export interface LinkLibrary { Team: (props: AnyProps & { team: string }) => RE | null; /** Teams */ Teams: NoRequiredProps; - /** Team's settings page */ + /** Team's settings profile page */ TeamSettings: (props: AnyProps & { team: string }) => RE | null; + /** Team's settings members page */ + TeamSettingsMembers: (props: AnyProps & { team: string }) => RE | null; + /** Team's settings service accounts page */ + TeamSettingsServiceAccounts: ( + props: AnyProps & { team: string } + ) => RE | null; + /** Team's actual settings page */ + TeamSettingsSettings: (props: AnyProps & { team: string }) => RE | null; /** Terms of service */ TermsOfService: NoRequiredProps; /** User */ @@ -137,6 +145,9 @@ const library: LinkLibrary = { Team: noop, Teams: noop, TeamSettings: noop, + TeamSettingsMembers: noop, + TeamSettingsServiceAccounts: noop, + TeamSettingsSettings: noop, TermsOfService: noop, }; diff --git a/packages/cyberstorm/src/components/Links/Links.tsx b/packages/cyberstorm/src/components/Links/Links.tsx index ca9c43031..eda874918 100644 --- a/packages/cyberstorm/src/components/Links/Links.tsx +++ b/packages/cyberstorm/src/components/Links/Links.tsx @@ -65,6 +65,9 @@ export type CyberstormLinkIds = | "Team" | "Teams" | "TeamSettings" + | "TeamSettingsMembers" + | "TeamSettingsServiceAccounts" + | "TeamSettingsSettings" | "TermsOfService" | "User"; diff --git a/packages/dapper-ts/src/methods/team.ts b/packages/dapper-ts/src/methods/team.ts index f2a7ce3a2..371be430b 100644 --- a/packages/dapper-ts/src/methods/team.ts +++ b/packages/dapper-ts/src/methods/team.ts @@ -52,7 +52,7 @@ export async function getTeamMembers( return parsed.data; } -const serviceAccountSchema = z +export const serviceAccountSchema = z .object({ identifier: z.string().uuid(), name: z.string().nonempty(), diff --git a/packages/thunderstore-api/src/fetch/teamAddMember.ts b/packages/thunderstore-api/src/fetch/teamAddMember.ts index 721fed849..a232eee4c 100644 --- a/packages/thunderstore-api/src/fetch/teamAddMember.ts +++ b/packages/thunderstore-api/src/fetch/teamAddMember.ts @@ -6,7 +6,7 @@ export type teamAddMemberMetaArgs = { }; export type teamAddMemberApiArgs = { - user: string; + username: string; role: string; }; @@ -15,7 +15,7 @@ export function teamAddMember( data: teamAddMemberApiArgs, meta: teamAddMemberMetaArgs ) { - const path = `/api/cyberstorm/team/${meta.teamIdentifier}/members/add/`; + const path = `/api/cyberstorm/team/${meta.teamIdentifier}/member/add/`; return apiFetch2({ config, diff --git a/packages/thunderstore-api/src/fetch/teamAddServiceAccount.ts b/packages/thunderstore-api/src/fetch/teamAddServiceAccount.ts index 0d8278f1e..bc9603ca5 100644 --- a/packages/thunderstore-api/src/fetch/teamAddServiceAccount.ts +++ b/packages/thunderstore-api/src/fetch/teamAddServiceAccount.ts @@ -14,7 +14,7 @@ export function teamAddServiceAccount( data: teamAddServiceAccountApiArgs, meta: teamAddServiceAccountMetaArgs ) { - const path = `api/cyberstorm/team/${meta.teamIdentifier}/service-accounts/create/`; + const path = `api/cyberstorm/team/${meta.teamIdentifier}/service-account/create/`; return apiFetch2({ config, diff --git a/packages/thunderstore-api/src/fetch/teamEditMember.ts b/packages/thunderstore-api/src/fetch/teamEditMember.ts index fb063b430..8d964a142 100644 --- a/packages/thunderstore-api/src/fetch/teamEditMember.ts +++ b/packages/thunderstore-api/src/fetch/teamEditMember.ts @@ -6,7 +6,7 @@ export type teamEditMemberMetaArgs = { }; export type teamEditMemberApiArgs = { - user: string; + username: string; role: string; }; diff --git a/packages/thunderstore-api/src/fetch/teamRemoveMember.ts b/packages/thunderstore-api/src/fetch/teamRemoveMember.ts index a43d078fb..99a6805f0 100644 --- a/packages/thunderstore-api/src/fetch/teamRemoveMember.ts +++ b/packages/thunderstore-api/src/fetch/teamRemoveMember.ts @@ -3,7 +3,7 @@ import { apiFetch2 } from "../apiFetch"; export type teamRemoveMemberMetaArgs = { teamIdentifier: string; - user: string; + username: string; }; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -21,7 +21,7 @@ export function teamRemoveMember( path, request: { method: "POST", - body: JSON.stringify({ user: meta.user }), + body: JSON.stringify({ username: meta.username }), }, }); } diff --git a/packages/thunderstore-api/src/fetch/teamServiceAccountRemove.ts b/packages/thunderstore-api/src/fetch/teamServiceAccountRemove.ts index 59c6e4dd8..20e094699 100644 --- a/packages/thunderstore-api/src/fetch/teamServiceAccountRemove.ts +++ b/packages/thunderstore-api/src/fetch/teamServiceAccountRemove.ts @@ -14,7 +14,7 @@ export function teamServiceAccountRemove( _data: teamServiceAccountRemoveApiArgs, meta: teamServiceAccountRemoveMetaArgs ) { - const path = `/api/cyberstorm/team/${meta.teamName}/service-accounts/delete/`; + const path = `/api/cyberstorm/team/${meta.teamName}/service-account/delete/`; return apiFetch2({ config, diff --git a/packages/ts-api-react-forms/src/schema.ts b/packages/ts-api-react-forms/src/schema.ts index 825e2d184..58dbd97ad 100644 --- a/packages/ts-api-react-forms/src/schema.ts +++ b/packages/ts-api-react-forms/src/schema.ts @@ -7,7 +7,7 @@ export const createTeamFormSchema = z.object({ }); export const teamAddMemberFormSchema = z.object({ - user: z + username: z .string({ required_error: "Username is required" }) .min(1, { message: "Username is required" }), role: z @@ -16,7 +16,7 @@ export const teamAddMemberFormSchema = z.object({ }); export const teamEditMemberFormSchema = z.object({ - user: z + username: z .string({ required_error: "Username is required" }) .min(1, { message: "Username is required" }), role: z