Skip to content

Commit

Permalink
Don't allow uploading a recording to an expired workspace (#10534)
Browse files Browse the repository at this point in the history
  • Loading branch information
hbenl authored May 24, 2024
1 parent 0a625be commit e19b3a3
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 10 deletions.
2 changes: 2 additions & 0 deletions packages/shared/graphql/generated/GetNonPendingWorkspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface GetNonPendingWorkspaces_viewer_workspaces_edges_node_members_ed

export interface GetNonPendingWorkspaces_viewer_workspaces_edges_node_members_edges_node_WorkspaceUserMember {
__typename: "WorkspaceUserMember";
id: string;
roles: string[];
user: GetNonPendingWorkspaces_viewer_workspaces_edges_node_members_edges_node_WorkspaceUserMember_user;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/shared/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ export interface Workspace {
id: string;
invitationCode?: string | null;
isDomainLimitedCode?: boolean | null;
members?: User[];
members?: WorkspaceUser[];
name?: string;
recordingCount?: number;
settings?: WorkspaceSettings | null;
Expand Down
19 changes: 19 additions & 0 deletions src/ui/components/UploadScreen/ExpiredWorkspaces.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.wrapper {
position: absolute;
top: -4rem;
background-color: #fffbd2;
color: #6e4400;
border: 1px solid #f7ea9c;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
cursor: default;
}

.warning {
font-weight: bold;
}

.wrapper a {
margin-left: 0.3rem;
text-decoration: underline;
}
41 changes: 41 additions & 0 deletions src/ui/components/UploadScreen/ExpiredWorkspaces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Workspace } from "shared/graphql/types";
import { useGetUserId } from "ui/hooks/users";
import { subscriptionExpired } from "ui/utils/workspace";

import styles from "./ExpiredWorkspaces.module.css";

export default function ExpiredWorkspaces({ workspaces }: { workspaces: Workspace[] }) {
const { userId } = useGetUserId();

const expiredWorkspaces = workspaces.filter(workspace => subscriptionExpired(workspace));

if (expiredWorkspaces.length === 0) {
return null;
}

const message =
expiredWorkspaces.length === 1
? `The workspace "${expiredWorkspaces[0].name}" has expired.`
: "Multiple workspaces have expired.";

const expiredWorkspaceWithAdminRights = expiredWorkspaces.find(workspace => {
const membership = workspace.members?.find(member => member.userId === userId);
return membership?.roles?.includes("admin");
});

return (
<div className={styles.wrapper}>
<span className={styles.warning}>Warning: </span>
{message}
{expiredWorkspaceWithAdminRights && (
<a
href={`/team/${expiredWorkspaceWithAdminRights.id}/settings/billing`}
target="_blank"
rel="noreferrer"
>
Go to billing page
</a>
)}
</div>
);
}
27 changes: 22 additions & 5 deletions src/ui/components/UploadScreen/TeamSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useState } from "react";

import { Workspace } from "shared/graphql/types";
import { useGetUserInfo } from "ui/hooks/users";
import { subscriptionExpired } from "ui/utils/workspace";

import PortalDropdown from "../shared/PortalDropdown";
import { Dropdown, DropdownItem } from "../shared/SharingModal/LibraryDropdown";
Expand All @@ -25,7 +26,8 @@ const TeamSelectButton = ({

interface DisplayedWorkspace {
id: string;
name?: string;
name: string;
expired?: boolean;
}

export default function TeamSelect({
Expand All @@ -39,15 +41,26 @@ export default function TeamSelect({
}) {
const userInfo = useGetUserInfo();
const [expanded, setExpanded] = useState(false);
let displayedWorkspaces: DisplayedWorkspace[] = [...workspaces].sort();
let displayedWorkspaces: DisplayedWorkspace[] = workspaces
.map(workspace => {
const expired = subscriptionExpired(workspace);
let name = workspace.name ?? "";
if (expired) {
name += " (Expired)";
}
return { id: workspace.id, name, expired };
})
.sort((a, b) => a.name.localeCompare(b.name));

if (userInfo.features.library) {
displayedWorkspaces = [personalWorkspace, ...displayedWorkspaces];
}

const handleSelect = (workspace: DisplayedWorkspace) => {
handleWorkspaceSelect(workspace.id);
setExpanded(false);
if (!workspace.expired) {
handleWorkspaceSelect(workspace.id);
setExpanded(false);
}
};

const selectedWorkspace =
Expand All @@ -67,7 +80,11 @@ export default function TeamSelect({
<Dropdown widthClass="w-64" fontSizeClass="text-base">
<div className="max-h-48 overflow-auto">
{displayedWorkspaces.map(workspace => (
<DropdownItem onClick={() => handleSelect(workspace)} key={workspace.id}>
<DropdownItem
disabled={workspace.expired}
onClick={() => handleSelect(workspace)}
key={workspace.id}
>
{workspace.name || ""}
</DropdownItem>
))}
Expand Down
7 changes: 5 additions & 2 deletions src/ui/components/UploadScreen/UploadScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import Modal from "ui/components/shared/NewModal";
import hooks from "ui/hooks";
import { useGetRecordingId } from "ui/hooks/recordings";
import { trackEvent } from "ui/utils/telemetry";
import { decodeWorkspaceId } from "ui/utils/workspace";
import { decodeWorkspaceId, subscriptionExpired } from "ui/utils/workspace";

import Icon from "../shared/Icon";
import LoadingScreen from "../shared/LoadingScreen";
import ReplayLogo from "../shared/ReplayLogo";
import { DefaultViewportWrapper } from "../shared/Viewport";
import ExpiredWorkspaces from "./ExpiredWorkspaces";
import { MY_LIBRARY } from "./libraryConstants";
import ReplayTitle from "./ReplayTitle";
import Sharing from "./Sharing";
Expand Down Expand Up @@ -133,7 +134,8 @@ export default function UploadScreen({
return MY_LIBRARY;
}

if (workspaces.find(workspace => workspace.id === workspaceId)) {
const workspace = workspaces.find(workspace => workspace.id === workspaceId);
if (workspace && !subscriptionExpired(workspace)) {
return workspaceId;
}

Expand Down Expand Up @@ -189,6 +191,7 @@ export default function UploadScreen({
return (
<DefaultViewportWrapper>
<div className="flex flex-col items-center">
<ExpiredWorkspaces workspaces={workspaces} />
<UploadRecordingTrialEnd {...{ selectedWorkspaceId, workspaces }} />
<form className="relative flex flex-col items-center overflow-auto" onSubmit={onSubmit}>
<div className="mb-10 flex flex-row space-x-4 short:h-auto">
Expand Down
12 changes: 10 additions & 2 deletions src/ui/hooks/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import {
UpdateWorkspaceSettings,
UpdateWorkspaceSettingsVariables,
} from "shared/graphql/generated/UpdateWorkspaceSettings";
import { Subscription, Workspace } from "shared/graphql/types";
import { Subscription, Workspace, WorkspaceUserRole } from "shared/graphql/types";
import {
ACTIVATE_WORKSPACE_SUBSCRIPTION,
ADD_WORKSPACE_API_KEY,
Expand Down Expand Up @@ -206,6 +206,8 @@ export function useGetNonPendingWorkspaces(): { workspaces: Workspace[]; loading
edges {
node {
... on WorkspaceUserMember {
id
roles
user {
id
name
Expand Down Expand Up @@ -237,7 +239,13 @@ export function useGetNonPendingWorkspaces(): { workspaces: Workspace[]; loading
.filter(({ node }) => "user" in node)
.map(({ node }) => {
assert("user" in node, "No user in workspace member");
return node.user;
return {
membershipId: node.id,
userId: node.user.id,
pending: false,
user: node.user,
roles: node.roles as WorkspaceUserRole[],
};
});
return {
...node,
Expand Down

0 comments on commit e19b3a3

Please sign in to comment.