Skip to content

Commit

Permalink
Add team settings pages
Browse files Browse the repository at this point in the history
  • Loading branch information
Oksamies committed Aug 26, 2024
1 parent dff2917 commit 4452ad1
Show file tree
Hide file tree
Showing 36 changed files with 1,115 additions and 53 deletions.
1 change: 0 additions & 1 deletion apps/cyberstorm-remix/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ function Root() {
envStuff: {
ENV: publicEnvVariables;
};
sessionId: string | null;
currentUser: CurrentUser;
} = JSON.parse(JSON.stringify(loaderOutput));

Expand Down
56 changes: 56 additions & 0 deletions apps/cyberstorm-remix/app/settings/teams/team/Tabs.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
100 changes: 100 additions & 0 deletions apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/Members.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<SettingItem
title="Members"
description="Your best buddies"
additionalLeftColumnContent={
<Dialog.Root
title="Add Member"
trigger={
<Button.Root colorScheme="primary" paddingSize="large">
<Button.ButtonLabel>Add Member</Button.ButtonLabel>
<Button.ButtonIcon>
<FontAwesomeIcon icon={faPlus} />
</Button.ButtonIcon>
</Button.Root>
}
>
<AddTeamMemberForm
updateTrigger={addTeamMemberRevalidate}
teamName={teamName}
/>
</Dialog.Root>
}
content={<TeamMemberList members={members} teamName={teamName} />}
/>
);
}
Original file line number Diff line number Diff line change
@@ -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";

Check failure

Code scanning / ESLint

Disallow unused variables Error

'useState' is defined but never used.
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: (
<CyberstormLink
linkId="User"
key={`user_${index}`}
user={member.username}
>
<div className={styles.userInfo}>
<Avatar
src={member.avatar}
username={member.username}
size="small"
/>
<span className={styles.userInfoName}>{member.username}</span>
</div>
</CyberstormLink>
),
sortValue: member.username,
},
{
value: (
<div key={`role_${index}`} className={styles.roleSelect}>
<TeamMemberChangeRoleAction
teamName={props.teamName}
userName={member.username}
currentRole={member.role}
updateTrigger={teamMemberRevalidate}
/>
</div>
),
sortValue: member.role,
},
{
value: (
<Dialog.Root
key={`action_${index}`}
title="Confirm member removal"
trigger={
<Button.Root
colorScheme="danger"
paddingSize="large"
key={`action_button_${index}`}
>
<Button.ButtonIcon>
<FontAwesomeIcon icon={faTrashCan} />
</Button.ButtonIcon>
<Button.ButtonLabel>Kick</Button.ButtonLabel>
</Button.Root>
}
>
<RemoveTeamMemberForm
teamName={props.teamName}
userName={member.username}
updateTrigger={teamMemberRevalidate}
/>
</Dialog.Root>
),
sortValue: 0,
},
];
});

return (
<Table
headers={teamMemberColumns}
rows={tableData}
sortByHeader={1}
sortDirection={Sort.ASC}
variant="itemList"
/>
);
}

TeamMemberList.displayName = "TeamMemberList";
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 4452ad1

Please sign in to comment.