fix: board and workspace URL links and copy behavior (#357)

- Use /boards/{id} for private board links so copied link works; keep pretty URL for public boards
- Show matching path in board header (pretty path for public, boards/id for private)
- Copy link button copies URL to clipboard instead of opening in new tab (opening a new tab seems redundant since we're already on it)
- Edit workspace URL button links to /settings/workspace instead of account settings
This commit is contained in:
Charity
2026-02-06 18:31:08 -05:00
committed by GitHub
parent 1ab4e24c96
commit f9eee53ed0
3 changed files with 60 additions and 31 deletions

View File

@@ -1,24 +1,40 @@
import Link from "next/link"; import { Fragment } from "react";
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro";
import { env } from "next-runtime-env"; import { env } from "next-runtime-env";
import { HiLink } from "react-icons/hi"; import { HiLink } from "react-icons/hi";
import { Tooltip } from "~/components/Tooltip"; import { Tooltip } from "~/components/Tooltip";
import { usePopup } from "~/providers/popup";
const displayBaseUrl =
env("NEXT_PUBLIC_KAN_ENV") === "cloud"
? "kan.bn"
: env("NEXT_PUBLIC_BASE_URL");
const linkBaseUrl = env("NEXT_PUBLIC_BASE_URL");
const pathSeparator = (
<div className="mx-1.5 h-4 w-px rotate-[20deg] bg-gray-300 dark:bg-dark-600" />
);
const UpdateBoardSlugButton = ({ const UpdateBoardSlugButton = ({
handleOnClick, handleOnClick,
workspaceSlug, workspaceSlug,
boardSlug, boardSlug,
boardPublicId,
visibility,
isLoading, isLoading,
canEdit, canEdit,
}: { }: {
handleOnClick: () => void; handleOnClick: () => void;
workspaceSlug: string; workspaceSlug: string;
boardSlug: string; boardSlug: string;
boardPublicId: string;
visibility: "public" | "private";
isLoading: boolean; isLoading: boolean;
canEdit: boolean; canEdit: boolean;
}) => { }) => {
if (!isLoading && (!workspaceSlug || !boardSlug)) return <></>; const { showPopup } = usePopup();
if (isLoading) { if (isLoading) {
return ( return (
@@ -26,44 +42,55 @@ const UpdateBoardSlugButton = ({
); );
} }
if (!workspaceSlug || !boardSlug || !boardPublicId) return <></>;
const isPublic = visibility === "public";
const boardUrl = isPublic
? `${linkBaseUrl}/${workspaceSlug}/${boardSlug}`
: `${linkBaseUrl}/boards/${boardPublicId}`;
const pathSegments = isPublic
? [displayBaseUrl, workspaceSlug, boardSlug]
: [displayBaseUrl, "boards", boardPublicId];
return ( return (
<Tooltip <Tooltip
content={!canEdit && !isLoading ? t`You don't have permission` : undefined} content={!canEdit ? t`You don't have permission` : undefined}
> >
<button <button
onClick={canEdit ? handleOnClick : undefined} onClick={canEdit ? handleOnClick : undefined}
disabled={!canEdit || isLoading} disabled={!canEdit || isLoading}
className="hidden cursor-pointer items-center gap-2 rounded-full border-[1px] bg-light-50 p-1 pl-4 pr-1 text-sm text-light-950 hover:bg-light-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-dark-600 dark:bg-dark-50 dark:text-dark-900 dark:hover:bg-dark-100 xl:flex" className="hidden cursor-pointer items-center gap-2 rounded-full border-[1px] bg-light-50 p-1 pl-4 pr-1 text-sm text-light-950 hover:bg-light-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-dark-600 dark:bg-dark-50 dark:text-dark-900 dark:hover:bg-dark-100 xl:flex"
> >
<div className="flex items-center"> <div className="flex items-center">
<span> {pathSegments.map((segment, i) => (
{env("NEXT_PUBLIC_KAN_ENV") === "cloud" <Fragment key={i}>
? "kan.bn" {i > 0 && pathSeparator}
: env("NEXT_PUBLIC_BASE_URL")} <span>{segment}</span>
</span> </Fragment>
<div className="mx-1.5 h-4 w-px rotate-[20deg] bg-gray-300 dark:bg-dark-600"></div> ))}
<span>{workspaceSlug}</span> </div>
<div className="mx-1.5 h-4 w-px rotate-[20deg] bg-gray-300 dark:bg-dark-600"></div> <button
<span>{boardSlug}</span> type="button"
</div> onClick={(e) => {
<Link e.stopPropagation();
href={`${env("NEXT_PUBLIC_BASE_URL")}/${workspaceSlug}/${boardSlug}`} navigator.clipboard.writeText(boardUrl).then(
target="_blank" () =>
rel="noopener noreferrer" showPopup({
onClick={(e) => { header: t`Link copied`,
e.stopPropagation(); icon: "success",
if (!canEdit) { message: t`Board URL copied to clipboard`,
e.preventDefault(); }),
} ).catch(() => undefined);
}} }}
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-light-200 dark:hover:bg-dark-200" className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-light-200 dark:hover:bg-dark-200"
> aria-label={t`Copy board link`}
<HiLink className="h-[13px] w-[13px]" /> >
</Link> <HiLink className="h-[13px] w-[13px]" />
</button> </button>
</button>
</Tooltip> </Tooltip>
); );
}; };
export default UpdateBoardSlugButton; export default UpdateBoardSlugButton;

View File

@@ -156,7 +156,7 @@ export function UpdateBoardSlugForm({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="secondary" variant="secondary"
href="/settings?tab=workspace" href="/settings/workspace"
onClick={closeModal} onClick={closeModal}
> >
{t`Edit workspace URL`} {t`Edit workspace URL`}

View File

@@ -443,6 +443,8 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
isLoading={isLoading} isLoading={isLoading}
workspaceSlug={workspace.slug ?? ""} workspaceSlug={workspace.slug ?? ""}
boardSlug={boardData?.slug ?? ""} boardSlug={boardData?.slug ?? ""}
boardPublicId={boardId ?? ""}
visibility={boardData?.visibility ?? "private"}
canEdit={canEditBoard} canEdit={canEditBoard}
/> />
<VisibilityButton <VisibilityButton