Skip to content

Commit

Permalink
Add /teams page to remix
Browse files Browse the repository at this point in the history
  • Loading branch information
Oksamies committed Aug 26, 2024
1 parent 1256a61 commit 0a5eb45
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 20 deletions.
2 changes: 1 addition & 1 deletion apps/cyberstorm-remix/app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function handleRequest(
remixContext: EntryContext,
loadContext: AppLoadContext
) {
let prohibitOutOfOrderStreaming =
const prohibitOutOfOrderStreaming =
isBotRequest(request.headers.get("user-agent")) || remixContext.isSpaMode;

return prohibitOutOfOrderStreaming
Expand Down
21 changes: 15 additions & 6 deletions apps/cyberstorm-remix/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ScrollRestoration,
isRouteErrorResponse,
useLoaderData,
useLocation,
useRouteError,
} from "@remix-run/react";
// import { LinksFunction } from "@remix-run/react/dist/routeModules";
Expand Down Expand Up @@ -105,6 +106,9 @@ export async function clientLoader() {
}

// REMIX TODO: Do we want to force a hydration at the root level?
// Answer: Yes and no, without manual hydrating the header will not update
// to have the user stuff, since the initial data from server loader will be used
// until data changes or updates
// clientLoader.hydrate = true;

const adContainerIds = ["right-column-1", "right-column-2", "right-column-3"];
Expand All @@ -119,6 +123,9 @@ function Root() {
currentUser: CurrentUser;
} = JSON.parse(JSON.stringify(loaderOutput));

const location = useLocation();
const shouldShowAds = !location.pathname.startsWith("/teams");

return (
<html lang="en">
<head>
Expand Down Expand Up @@ -149,9 +156,11 @@ function Root() {
<Outlet />
</div>
<div className={styles.sideContainers}>
{adContainerIds.map((cid, k_i) => (
<AdContainer key={k_i} containerId={cid} noHeader />
))}
{shouldShowAds
? adContainerIds.map((cid, k_i) => (
<AdContainer key={k_i} containerId={cid} noHeader />
))
: null}
</div>
</section>
<Footer />
Expand All @@ -162,7 +171,7 @@ function Root() {
</SessionProvider>
<ScrollRestoration />
<Scripts />
<AdsInit />
{shouldShowAds ? <AdsInit /> : null}
</body>
</html>
);
Expand Down Expand Up @@ -264,9 +273,9 @@ function AdsInit() {

const nitroAds = typeof window !== "undefined" ? window.nitroAds : undefined;
useEffect(() => {
if (nitroAds !== undefined) {
if (nitroAds !== undefined && nitroAds.createAd !== undefined) {
adContainerIds.forEach((cid) => {
if (nitroAds !== undefined) {
if (nitroAds !== undefined && nitroAds.createAd !== undefined) {
nitroAds.createAd(cid, {
demo: false,
format: "display",
Expand Down
142 changes: 142 additions & 0 deletions apps/cyberstorm-remix/app/settings/teams/Teams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import type { MetaFunction } from "@remix-run/node";
import { useLoaderData, useRevalidator } from "@remix-run/react";
import {
BreadCrumbs,
Button,
CyberstormLink,
Dialog,
PageHeader,
SettingItem,
Table,
} from "@thunderstore/cyberstorm";
import { faPlus } from "@fortawesome/pro-solid-svg-icons";
import styles from "./TeamsLayout.module.css";
import rootStyles from "../../RootLayout.module.css";
import { getDapper } from "cyberstorm/dapper/sessionUtils";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import { CreateTeamForm } from "@thunderstore/cyberstorm-forms";
import { currentUserSchema } from "@thunderstore/dapper-ts";

export const meta: MetaFunction<typeof loader | typeof clientLoader> = ({
data,
}) => {
return [
{ title: `Teams of ${data?.currentUser.username}` },
{ name: "description", content: `Teams of ${data?.currentUser.username}` },
];
};

export async function loader() {
const dapper = await getDapper();
const currentUser = await dapper.getCurrentUser();
return {
currentUser: currentUser as typeof currentUserSchema._type,
};
}

export async function clientLoader() {
const dapper = await getDapper(true);
const currentUser = await dapper.getCurrentUser();
if (!currentUser.username) {
throw new Response("Not logged in.", { status: 401 });
} else {
return {
currentUser: currentUser as typeof currentUserSchema._type,
};
}
}

clientLoader.hydrate = true;

// REMIX TODO: The "Create team" form is missing data refetching after creating a team
// Copy the stuff from package liking
export default function Teams() {
const { currentUser } = useLoaderData<typeof loader | typeof clientLoader>();
const [dialogOpen, setOpenDialog] = useState(false);
const revalidator = useRevalidator();
const [isRefetching, setIsRefetching] = useState(false);

useEffect(() => {
setIsRefetching(false);
}, [currentUser]);

// REMIX TODO: Move current user to stand-alone loader and revalidate only currentUser
async function createTeamRevalidate() {
if (!isRefetching) {
setIsRefetching(true);
revalidator.revalidate();
}
}

return (
<>
<BreadCrumbs>
<CyberstormLink linkId="Teams">Teams</CyberstormLink>
</BreadCrumbs>
<header className={rootStyles.pageHeader}>
<PageHeader title="Teams" />
</header>
<main className={rootStyles.main}>
<SettingItem
title="Teams"
description="Manage your teams"
additionalLeftColumnContent={
<Dialog.Root
open={dialogOpen}
onOpenChange={setOpenDialog}
title="Create Team"
trigger={
<Button.Root colorScheme="primary" paddingSize="large">
<Button.ButtonLabel>Create team</Button.ButtonLabel>
<Button.ButtonIcon>
<FontAwesomeIcon icon={faPlus} />
</Button.ButtonIcon>
</Button.Root>
}
>
<CreateTeamForm
dialogOnChange={setOpenDialog}
updateTrigger={createTeamRevalidate}
/>
</Dialog.Root>
}
content={
<div className={styles.contentWrapper}>
<Table
headers={[
{ value: "Team Name", disableSort: false },
{ value: "Role", disableSort: false },
{ value: "Members", disableSort: false },
]}
rows={currentUser.teams.map((team) => [
{
value: (
<CyberstormLink
linkId="TeamSettings"
key={team.name}
team={team.name}
>
<span className={styles.nameColumn}>{team.name}</span>
</CyberstormLink>
),
sortValue: team.name,
},
{
value: team.role,
sortValue: team.role,
},
{
value: team.member_count,
sortValue: team.member_count,
},
])}
variant="itemList"
/>
</div>
}
/>
</main>
</>
);
}
18 changes: 18 additions & 0 deletions apps/cyberstorm-remix/app/settings/teams/TeamsLayout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.contentWrapper {
display: flex;
flex-direction: column;
gap: var(--gap--16);
}

.boldText {
font-weight: var(--font-weight-bold);
}

/* TODO: Text in header row and content rows are not aligned */

/* TODO: why do we have this hover color, and should it apply to hover
on the whole row, not just one column?
*/
.nameColumn:hover {
color: rgb(57 233 170);
}
17 changes: 9 additions & 8 deletions apps/cyberstorm-remix/cyberstorm/dapper/sessionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ export async function getDapper(isClient = false) {
const dapper = window.Dapper;

let shouldRemakeDapper = false;

const allCookies = new URLSearchParams(
window.document.cookie.replaceAll("&", "%26").replaceAll("; ", "&")
);
const cookie = allCookies.get("sessionid");
const csrftoken = allCookies.get("csrftoken");

const newConfig: RequestConfig = {
apiHost: window.ENV.PUBLIC_API_URL,
sessionId: undefined,
csrfToken: undefined,
sessionId: cookie ?? undefined,
csrfToken: csrftoken ?? undefined,
};

if (dapper) {
Expand All @@ -20,12 +27,6 @@ export async function getDapper(isClient = false) {
// shouldRemakeDapper = true;
// }

const allCookies = new URLSearchParams(
window.document.cookie.replaceAll("&", "%26").replaceAll("; ", "&")
);
const cookie = allCookies.get("sessionid");
const csrftoken = allCookies.get("csrftoken");

// sessionId is old
if (
dapper.config.sessionId &&
Expand Down
14 changes: 12 additions & 2 deletions apps/cyberstorm-remix/cyberstorm/navigation/UserActions.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { faSignOut } from "@fortawesome/pro-solid-svg-icons";
import { faSignOut, faUsers } from "@fortawesome/pro-solid-svg-icons";
import { faDiscord, faGithub } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import styles from "./Navigation.module.css";
import { Avatar, Icon } from "@thunderstore/cyberstorm";
import { Avatar, CyberstormLink, Icon } from "@thunderstore/cyberstorm";
import { DropDownLink } from "@thunderstore/cyberstorm/src/components/DropDown/DropDownLink";
import {
OverwolfLogo,
Expand All @@ -28,6 +28,16 @@ export function UserActions(props: { user: CurrentUser }) {
</div>
</div>
</div>,
<CyberstormLink
linkId="Teams"
key="teams"
className={styles.mobileNavAccountPopoverItem}
>
<DropDownLink
leftIcon={<FontAwesomeIcon icon={faUsers} />}
label="Teams"
/>
</CyberstormLink>,
<a
href="/logout"
key="logout"
Expand Down
14 changes: 14 additions & 0 deletions apps/cyberstorm-remix/cyberstorm/navigation/UserDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Avatar,
DropDownDivider,
DropDownItem,
CyberstormLink,
} from "@thunderstore/cyberstorm";
import { AvatarButton } from "@thunderstore/cyberstorm/src/components/Avatar/AvatarButton";
import { DropDownLink } from "@thunderstore/cyberstorm/src/components/DropDown/DropDownLink";
Expand Down Expand Up @@ -40,6 +41,19 @@ export function UserDropDown(props: Props) {

<DropDownDivider key="divider-first" />,

<CyberstormLink linkId="Teams" key="teams">
<DropDownItem
content={
<DropDownLink
leftIcon={<FontAwesomeIcon icon={faUsers} />}
label="Teams"
/>
}
/>
</CyberstormLink>,

<DropDownDivider key="divider-first" />,

<a href="/logout" key="logout">
<DropDownItem
content={
Expand Down
1 change: 1 addition & 0 deletions apps/cyberstorm-remix/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default defineConfig({
"/c/:communityId/p/:namespaceId/:packageId/dependants",
"p/dependants/Dependants.tsx"
);
route("/teams", "settings/teams/Teams.tsx");
});
},
}),
Expand Down
6 changes: 3 additions & 3 deletions nginx/conf.d/remixdefault.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ server {
proxy_pass http://host.docker.internal:3000/communities;
}

# location /teams {
# proxy_pass http://host.docker.internal:3000/teams;
# }
location /teams {
proxy_pass http://host.docker.internal:3000/teams;
}

location /c {
proxy_pass http://host.docker.internal:3000/c;
Expand Down
2 changes: 2 additions & 0 deletions packages/cyberstorm-forms/src/forms/CreateTeamForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {

export function CreateTeamForm(props: {
dialogOnChange: (v: boolean) => void;
updateTrigger: () => Promise<void>;
}) {
const { onSubmitSuccess, onSubmitError } = useFormToaster({
successMessage: "Team created",
Expand All @@ -22,6 +23,7 @@ export function CreateTeamForm(props: {
return (
<ApiForm
onSubmitSuccess={() => {
props.updateTrigger();
onSubmitSuccess();
props.dialogOnChange(false);
}}
Expand Down
1 change: 1 addition & 0 deletions packages/dapper-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getCommunities, getCommunity } from "./methods/communities";
import { getCommunityFilters } from "./methods/communityFilters";
import { getCurrentUser, emptyUser } from "./methods/currentUser";
export const getEmptyUser = emptyUser;
export { currentUserSchema } from "./methods/currentUser";
import {
getPackageChangelog,
getPackageReadme,
Expand Down
12 changes: 12 additions & 0 deletions packages/dapper-ts/src/methods/currentUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ const teamSchema = z.object({
member_count: z.number().int().gte(1),
});

export const currentUserSchema = z.object({
username: z.string().nonempty(),
capabilities: z.string().array(),
connections: oAuthConnectionSchema.array(),
rated_packages: z.string().array(),
rated_packages_cyberstorm: z.string().array(),
subscription: z.object({
expires: z.string().datetime().nullable(),
}),
teams: teamSchema.array(),
});

const schema = z.object({
username: z.string().nonempty(),
capabilities: z.string().array(),
Expand Down

0 comments on commit 0a5eb45

Please sign in to comment.