diff --git a/components/dashboard/src/components/podkit/buttons/LinkButton.tsx b/components/dashboard/src/components/podkit/buttons/LinkButton.tsx index 3e8cf39bd27805..2083f59b3714ab 100644 --- a/components/dashboard/src/components/podkit/buttons/LinkButton.tsx +++ b/components/dashboard/src/components/podkit/buttons/LinkButton.tsx @@ -6,27 +6,30 @@ import { Link } from "react-router-dom"; import { Button, ButtonProps } from "@podkit/buttons/Button"; -import React from "react"; +import { forwardRef, HTMLAttributeAnchorTarget } from "react"; export interface LinkButtonProps extends ButtonProps { asChild?: false; href: string; + target?: HTMLAttributeAnchorTarget; isExternalUrl?: boolean; } /** * A HTML anchor element styled as a button. */ -export const LinkButton = React.forwardRef( - ({ asChild, children, href, ...props }, ref) => { +export const LinkButton = forwardRef( + ({ asChild, children, href, target, ...props }, ref) => { return ( ); diff --git a/components/dashboard/src/start/StartWorkspace.tsx b/components/dashboard/src/start/StartWorkspace.tsx index bb0263710cac51..e49bc754073cd5 100644 --- a/components/dashboard/src/start/StartWorkspace.tsx +++ b/components/dashboard/src/start/StartWorkspace.tsx @@ -36,6 +36,7 @@ import { import { PartialMessage } from "@bufbuild/protobuf"; import { trackEvent } from "../Analytics"; import { fromWorkspaceName } from "../workspaces/RenameWorkspaceModal"; +import { LinkButton } from "@podkit/buttons/LinkButton"; const sessionId = v4(); @@ -102,6 +103,14 @@ export interface StartWorkspaceState { ideOptions?: IDEOptions; isSSHModalVisible?: boolean; ownerToken?: string; + /** + * Set to prevent multiple redirects to the same URL when the User Agent ignores our wish to open links in the same tab (by setting window.location.href). + */ + redirected?: boolean; + /** + * Determines whether `redirected` has been `true` for long enough to display our "new tab" info banner without racing with same-tab redirection in regular setups + */ + showRedirectMessage?: boolean; } // TODO: use Function Components @@ -183,7 +192,7 @@ export default class StartWorkspace extends React.Component { + this.setState({ showRedirectMessage: true }); + }, 2000); } private openDesktopLink(link: string) { @@ -503,7 +521,7 @@ export default class StartWorkspace extends React.Component; // Pending means the workspace does not yet consume resources in the cluster, but rather is looking for - // some space within the cluster. If for example the cluster needs to scale up to accomodate the + // some space within the cluster. If for example the cluster needs to scale up to accommodate the // workspace, the workspace will be in Pending state until that happened. case WorkspacePhase_Phase.PENDING: phase = StartPhase.Preparing; @@ -746,6 +764,7 @@ export default class StartWorkspace extends React.Component {statusMessage} + {this.state.showRedirectMessage && ( + <> + + We redirected you to your workspace, but your browser probably opened it in another tab. + + +
+ + Go to Dashboard + + {this.state.workspace?.status?.workspaceUrl && + this.state.workspace.status.phase?.name === WorkspacePhase_Phase.RUNNING && ( + + Re-open Workspace + + )} +
+ + )} ); }