Skip to content

Commit e3fffe2

Browse files
committed
Added CopyableText component, used for waitpoint id in the table
1 parent d24e976 commit e3fffe2

File tree

3 files changed

+86
-4
lines changed

3 files changed

+86
-4
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useCallback, useState } from "react";
2+
import { SimpleTooltip } from "~/components/primitives/Tooltip";
3+
import { ClipboardCheckIcon, ClipboardIcon } from "lucide-react";
4+
import { cn } from "~/utils/cn";
5+
6+
export function CopyableText({ value, className }: { value: string; className?: string }) {
7+
const [isHovered, setIsHovered] = useState(false);
8+
const [copied, setCopied] = useState(false);
9+
10+
const copy = useCallback(
11+
(e: React.MouseEvent) => {
12+
e.preventDefault();
13+
e.stopPropagation();
14+
navigator.clipboard.writeText(value);
15+
setCopied(true);
16+
setTimeout(() => {
17+
setCopied(false);
18+
}, 1500);
19+
},
20+
[value]
21+
);
22+
23+
return (
24+
<span
25+
className={cn("group relative inline-flex h-6 items-center", className)}
26+
onMouseLeave={() => setIsHovered(false)}
27+
>
28+
<span onMouseEnter={() => setIsHovered(true)}>{value}</span>
29+
<span
30+
onClick={copy}
31+
onMouseDown={(e) => e.stopPropagation()}
32+
className={cn(
33+
"absolute -right-6 top-0 z-10 size-6 font-sans",
34+
isHovered ? "flex" : "hidden"
35+
)}
36+
>
37+
<SimpleTooltip
38+
button={
39+
<span
40+
className={cn(
41+
"ml-1 flex size-6 items-center justify-center rounded border border-charcoal-650 bg-charcoal-750",
42+
copied
43+
? "text-green-500"
44+
: "text-text-dimmed hover:border-charcoal-600 hover:bg-charcoal-700 hover:text-text-bright"
45+
)}
46+
>
47+
{copied ? (
48+
<ClipboardCheckIcon className="size-3.5" />
49+
) : (
50+
<ClipboardIcon className="size-3.5" />
51+
)}
52+
</span>
53+
}
54+
content={copied ? "Copied!" : "Copy text"}
55+
className="font-sans"
56+
disableHoverableContent
57+
/>
58+
</span>
59+
</span>
60+
);
61+
}

apps/webapp/app/presenters/v3/WaitpointTokenListPresenter.server.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { type Direction } from "~/components/ListPagination";
44
import { sqlDatabaseSchema } from "~/db.server";
55
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
66
import { BasePresenter } from "./basePresenter.server";
7-
import { type WaitpointFilterStatus } from "~/components/runs/v3/WaitpointTokenFilters";
7+
import {
8+
type WaitpointFilterStatus,
9+
type WaitpointSearchParams,
10+
} from "~/components/runs/v3/WaitpointTokenFilters";
811
import { determineEngineVersion } from "~/v3/engineVersion.server";
912

1013
const DEFAULT_PAGE_SIZE = 25;
@@ -46,6 +49,7 @@ type Result =
4649
previous: string | undefined;
4750
};
4851
hasFilters: boolean;
52+
filters: WaitpointSearchParams;
4953
}
5054
| {
5155
success: false;
@@ -57,6 +61,7 @@ type Result =
5761
previous: undefined;
5862
};
5963
hasFilters: false;
64+
filters: undefined;
6065
};
6166

6267
export class WaitpointTokenListPresenter extends BasePresenter {
@@ -85,6 +90,7 @@ export class WaitpointTokenListPresenter extends BasePresenter {
8590
previous: undefined,
8691
},
8792
hasFilters: false,
93+
filters: undefined,
8894
};
8995
}
9096

@@ -260,6 +266,17 @@ export class WaitpointTokenListPresenter extends BasePresenter {
260266
previous,
261267
},
262268
hasFilters,
269+
filters: {
270+
id,
271+
statuses: statuses?.length ? statuses : undefined,
272+
tags: tags?.length ? tags : undefined,
273+
idempotencyKey,
274+
period,
275+
from,
276+
to,
277+
cursor,
278+
direction,
279+
},
263280
};
264281
}
265282
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { WaitpointTokenListPresenter } from "~/presenters/v3/WaitpointTokenListP
4040
import { requireUserId } from "~/services/session.server";
4141
import { docsPath, EnvironmentParamSchema, v3WaitpointTokenPath } from "~/utils/pathBuilder";
4242
import { determineEngineVersion } from "~/v3/engineVersion.server";
43+
import { CopyableText } from "~/components/primitives/CopyableText";
4344

4445
export const meta: MetaFunction = () => {
4546
return [
@@ -102,7 +103,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
102103
};
103104

104105
export default function Page() {
105-
const { success, tokens, pagination, hasFilters } = useTypedLoaderData<typeof loader>();
106+
const { success, tokens, pagination, hasFilters, filters } = useTypedLoaderData<typeof loader>();
106107

107108
const organization = useOrganization();
108109
const project = useProject();
@@ -160,7 +161,8 @@ export default function Page() {
160161
organization,
161162
project,
162163
environment,
163-
token
164+
token,
165+
filters
164166
);
165167

166168
return (
@@ -170,7 +172,9 @@ export default function Page() {
170172
<DateTime date={token.createdAt} />
171173
</span>
172174
</TableCell>
173-
<TableCell to={path}>{token.friendlyId}</TableCell>
175+
<TableCell to={path}>
176+
<CopyableText value={token.friendlyId} className="font-mono" />
177+
</TableCell>
174178
<TableCell to={path}>
175179
<WaitpointStatusCombo
176180
status={token.status}

0 commit comments

Comments
 (0)