diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/client.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/client.tsx
index a8f7c41b0b..4a81afa0aa 100644
--- a/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/client.tsx
+++ b/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/client.tsx
@@ -1,6 +1,5 @@
"use client";
-import { PageHeader } from "@/components/dashboard/page-header";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { trpc } from "@/lib/trpc/client";
import {
@@ -63,26 +62,6 @@ export function TeamPageClient({ team }: { team: boolean }) {
return null;
}
- const actions: React.ReactNode[] = [];
-
- if (isAdmin) {
- actions.push(
- setTab(value)}>
-
-
-
-
-
- Members
- Invitations
-
-
- ,
- );
-
- actions.push( );
- }
-
if (!team) {
return (
@@ -103,7 +82,24 @@ export function TeamPageClient({ team }: { team: boolean }) {
return (
<>
-
+ {isAdmin ? (
+
+
+ setTab(value)}>
+
+
+
+
+
+ Members
+ Invitations
+
+
+
+
+
+
+ ) : null}
{isLoading || !user || !organization || !userMemberships || !currentOrgMembership ? (
) : tab === "members" ? (
diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/members.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/members.tsx
index 18047823eb..291adab8e2 100644
--- a/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/members.tsx
+++ b/apps/dashboard/app/(app)/[workspaceSlug]/settings/team/members.tsx
@@ -1,6 +1,5 @@
"use client";
-import { Confirm } from "@/components/dashboard/confirm";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Table,
@@ -12,8 +11,8 @@ import {
} from "@/components/ui/table";
import type { AuthenticatedUser, Membership, Organization } from "@/lib/auth/types";
import { trpc } from "@/lib/trpc/client";
-import { Button, Empty, Loading, toast } from "@unkey/ui";
-import { memo } from "react";
+import { Button, ConfirmPopover, Empty, Loading, toast } from "@unkey/ui";
+import { memo, useMemo, useState } from "react";
import { InviteButton } from "./invite";
import { RoleSwitcher } from "./role-switcher";
@@ -24,11 +23,16 @@ type MembersProps = {
};
export const Members = memo
(({ organization, user, userMembership }) => {
+ const [isConfirmPopoverOpen, setIsConfirmPopoverOpen] = useState(false);
+ const [currentMembership, setCurrentMembership] = useState(null);
+ const [anchorEl, setAnchorEl] = useState(null);
const { data: orgMemberships, isLoading } = trpc.org.members.list.useQuery(organization?.id);
const memberships = orgMemberships?.data;
const isAdmin = userMembership?.role === "admin";
const utils = trpc.useUtils();
+ const anchorRef = useMemo(() => ({ current: anchorEl }), [anchorEl]);
+
const removeMember = trpc.org.members.remove.useMutation({
onSuccess: () => {
// Invalidate the member list query to trigger a refetch
@@ -48,6 +52,15 @@ export const Members = memo(({ organization, user, userMembership
);
}
+ const handleDeleteButtonClick = (
+ membership: Membership,
+ event: React.MouseEvent,
+ ) => {
+ setAnchorEl(event.currentTarget);
+ setCurrentMembership(membership);
+ setIsConfirmPopoverOpen(true);
+ };
+
if (!memberships || memberships.length === 0) {
return (
@@ -59,69 +72,89 @@ export const Members = memo(({ organization, user, userMembership
}
return (
-
-
-
- Member
- Role
- {/*/ empty */}
-
-
-
- {memberships.map(({ id, role, user: member }) => (
-
-
-
-
-
-
- {member.fullName?.slice(0, 1) ?? member.email.slice(0, 1)}
-
-
-
-
- {`${member.firstName ? member.firstName : member.email} ${
- member.lastName ? member.lastName : ""
- }`}
-
-
- {member.firstName ? member.email : ""}
-
-
-
-
-
-
-
-
- {isAdmin && user && member.id !== user.id ? (
- {
- try {
- await removeMember.mutateAsync({
- orgId: organization.id,
- membershipId: id,
- });
- } catch (error) {
- console.error("Error removing member:", error);
- }
- }}
- trigger={(onClick) => Remove }
- />
- ) : null}
-
+ <>
+ {currentMembership && anchorEl ? (
+ {
+ try {
+ await removeMember.mutateAsync({
+ orgId: organization.id,
+ membershipId: currentMembership.id,
+ });
+ } catch (error) {
+ console.error("Error removing member:", error);
+ }
+ }}
+ />
+ ) : null}
+
+
+
+ Member
+ Role
+ {/*/ empty */}
- ))}
-
-
+
+
+ {memberships.map((membership) => {
+ const { id, role, user: member } = membership;
+ return (
+
+
+
+
+
+
+ {member.fullName?.slice(0, 1) ?? member.email.slice(0, 1)}
+
+
+
+
+ {`${member.firstName ? member.firstName : member.email} ${
+ member.lastName ? member.lastName : ""
+ }`}
+
+
+ {member.firstName ? member.email : ""}
+
+
+
+
+
+
+
+
+ {isAdmin && user && member.id !== user.id ? (
+ handleDeleteButtonClick(membership, event)}
+ >
+ Remove
+
+ ) : null}
+
+
+ );
+ })}
+
+
+ >
);
});
diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/client.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/client.tsx
index a2459585e0..bef0cde6ee 100644
--- a/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/client.tsx
+++ b/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/client.tsx
@@ -4,7 +4,6 @@
*/
"use client";
-import { PageHeader } from "@/components/dashboard/page-header";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
DropdownMenu,
@@ -91,19 +90,6 @@ export const Client: React.FC = ({ projects, integration, apis, rootKeys
return (
<>
-
- Configure Vercel
- ,
- ]}
- />
{projects.map((project) => {
diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/page.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/page.tsx
index aff7e031e7..4897594852 100644
--- a/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/page.tsx
+++ b/apps/dashboard/app/(app)/[workspaceSlug]/settings/vercel/page.tsx
@@ -3,7 +3,6 @@
* Hiding for now until we decide if we want to fix it up or toss it
*/
-import { Navbar as SubMenu } from "@/components/dashboard/navbar";
import { Navigation } from "@/components/navigation/navigation";
import { PageContent } from "@/components/page-content";
import { getAuth } from "@/lib/auth";
@@ -14,7 +13,6 @@ import { Button, Code, Empty } from "@unkey/ui";
import { Vercel } from "@unkey/vercel";
import Link from "next/link";
import { notFound } from "next/navigation";
-import { navigation } from "../constants";
import { Client } from "./client";
export const dynamic = "force-dynamic";
@@ -58,7 +56,6 @@ export default async function Page(props: Props) {
} />
-
Vercel is not connected to this workspace
@@ -184,7 +181,6 @@ export default async function Page(props: Props) {
} name="Settings" />
-
diff --git a/apps/dashboard/app/integrations/vercel/callback/client.tsx b/apps/dashboard/app/integrations/vercel/callback/client.tsx
index f73ad29c84..1b5b72b1fa 100644
--- a/apps/dashboard/app/integrations/vercel/callback/client.tsx
+++ b/apps/dashboard/app/integrations/vercel/callback/client.tsx
@@ -4,7 +4,6 @@
*/
"use client";
-import { PageHeader } from "@/components/dashboard/page-header";
import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { trpc } from "@/lib/trpc/client";
@@ -21,7 +20,7 @@ import {
} from "@unkey/ui";
import { useRouter } from "next/navigation";
import { useState } from "react";
-import { WorkspaceSwitcher } from "./workspace";
+// import { WorkspaceSwitcher } from "./workspace";
type Props = {
projects: { id: string; name: string }[];
apis: Api[];
@@ -70,12 +69,6 @@ export const Client: React.FC
= ({
return (
-
]}
- />
-
Vercel Project
diff --git a/apps/dashboard/components/dashboard/charts.tsx b/apps/dashboard/components/dashboard/charts.tsx
deleted file mode 100644
index a0a3b1f189..0000000000
--- a/apps/dashboard/components/dashboard/charts.tsx
+++ /dev/null
@@ -1,409 +0,0 @@
-"use client";
-
-import { Area, Bar, Column, Line } from "@ant-design/plots";
-import { useTheme } from "next-themes";
-
-type ColorName = "primary" | "warn" | "danger";
-
-export const useColors = (colorNames: Array
) => {
- const { resolvedTheme } = useTheme();
-
- const colors: {
- light: Record;
- dark: Record;
- } = {
- light: {
- primary: "#1c1917",
- warn: "#FFCD07",
- danger: "#D12542",
- },
- dark: {
- primary: "#f1efef",
- warn: "#FFE41C",
- danger: "#FF7568",
- },
- };
-
- return {
- color: resolvedTheme === "dark" ? "#f1efef" : "#1c1917",
- palette:
- resolvedTheme === "dark"
- ? colorNames.map((c) => colors.dark[c])
- : colorNames.map((c) => colors.light[c]),
- axisColor: resolvedTheme === "dark" ? "#1b1918" : "#e8e5e3",
- };
-};
-
-export type Props = {
- data: {
- x: string;
- y: number;
- }[];
- timeGranularity: "hour" | "day" | "month";
- tooltipLabel: string;
- colors?: Array;
- padding?: number[] | number | "auto";
-};
-
-export const AreaChart: React.FC = ({ data, timeGranularity, tooltipLabel, padding }) => {
- const { color, axisColor } = useColors(["primary", "warn", "danger"]);
- return (
- {
- switch (timeGranularity) {
- case "hour":
- return new Date(v).toLocaleTimeString();
- case "day":
- return new Date(v).toLocaleDateString();
- case "month":
- return new Date(v).toLocaleDateString(undefined, {
- month: "long",
- year: "numeric",
- });
- }
- },
- },
- }}
- yAxis={{
- tickCount: 3,
- label: {
- formatter: (v: string) =>
- Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(v)),
- },
- grid: {
- line: {
- style: {
- stroke: axisColor,
- },
- },
- },
- }}
- tooltip={{
- formatter: (datum) => ({
- name: tooltipLabel,
- value: Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(datum.y)),
- }),
- }}
- />
- );
-};
-
-export const LineChart: React.FC<{
- data: {
- category: string;
- x: string;
- y: number;
- }[];
-}> = ({ data }) => {
- return (
- ({
- name: datum.category,
- value: `${Intl.NumberFormat(undefined, {
- notation: "compact",
- }).format(Number(datum.y))} ms`,
- }),
- }}
- />
- );
-};
-
-export const ColumnChart: React.FC = ({ data, colors }) => {
- const { color, axisColor } = useColors(colors ?? ["primary", "warn", "danger"]);
- return (
- new Date(v).toLocaleString(),
- },
- tickLine: {
- style: {
- stroke: axisColor,
- },
- },
- line: {
- style: {
- stroke: axisColor,
- },
- },
- }}
- yAxis={{
- tickCount: 5,
- label: {
- formatter: (v: string) =>
- Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(v)),
- },
- grid: {
- line: {
- style: {
- stroke: axisColor,
- },
- },
- },
- }}
- tooltip={{
- formatter: (datum) => ({
- name: "Usage",
- value: Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(datum.y)),
- }),
- }}
- />
- );
-};
-
-export const StackedColumnChart: React.FC<{
- data: {
- category: string;
- x: string;
- y: number;
- }[];
- timeGranularity?: "minute" | "hour" | "day" | "month";
- colors: Array;
-}> = ({ data, timeGranularity, colors }) => {
- const { axisColor } = useColors(colors);
-
- const formatDate = (date: string) => {
- const d = new Date(date);
- if (Number.isNaN(d.getTime())) {
- return date;
- }
-
- switch (timeGranularity) {
- case "minute":
- return d.toLocaleString(undefined, {
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
- month: "short",
- day: "numeric",
- });
- case "hour":
- return d.toLocaleString(undefined, {
- hour: "numeric",
- hour12: true,
- month: "short",
- day: "numeric",
- year: "numeric",
- });
- case "day":
- return d.toLocaleString(undefined, {
- weekday: "short",
- month: "short",
- day: "numeric",
- year: "numeric",
- });
- case "month":
- return d.toLocaleString(undefined, {
- month: "long",
- year: "numeric",
- });
- default:
- return d.toLocaleString(undefined, {
- month: "short",
- day: "numeric",
- year: "numeric",
- hour: "numeric",
- minute: "2-digit",
- });
- }
- };
-
- return (
- {
- return {
- fill: "rgba(0,0,0,0.25)",
- stroke: oldStyle.fill,
- lineWidth: 0.5,
- };
- },
- }}
- xAxis={{
- label: {
- formatter: (v: string) => {
- switch (timeGranularity) {
- case "minute":
- return new Date(v).toLocaleTimeString();
- case "hour":
- return new Date(v).toLocaleTimeString();
- case "day":
- return new Date(v).toLocaleDateString();
- case "month":
- return new Date(v).toLocaleDateString(undefined, {
- month: "long",
- year: "numeric",
- });
- default:
- return v;
- }
- },
- },
- tickLine: {
- style: {
- stroke: axisColor,
- },
- },
- line: {
- style: {
- stroke: axisColor,
- },
- },
- }}
- yAxis={{
- tickCount: 5,
- label: {
- formatter: (v: string) =>
- Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(v)),
- },
- grid: {
- line: {
- style: {
- stroke: axisColor,
- },
- },
- },
- }}
- tooltip={{
- title: formatDate,
- formatter: (datum) => ({
- name: datum.category,
- value: Intl.NumberFormat(undefined, {
- notation: "compact",
- maximumFractionDigits: 1,
- compactDisplay: "short",
- }).format(Number(datum.y)),
- }),
- }}
- />
- );
-};
-
-export const StackedBarChart: React.FC<{
- data: {
- category: string;
- x: number;
- y: string;
- }[];
- colors: Array;
-}> = ({ data, colors }) => {
- const { palette, axisColor } = useColors(colors);
- return (
-
- d.x > 0 ? Intl.NumberFormat(undefined, { notation: "compact" }).format(d.x) : "",
- }}
- maxBarWidth={16}
- yAxis={{
- label: {
- formatter: (v: string) => {
- return v.length <= 16 ? v : `${v.slice(0, 16)}...`;
- },
- },
- tickLine: {
- style: {
- stroke: axisColor,
- },
- },
- line: {
- style: {
- stroke: axisColor,
- },
- },
- }}
- xAxis={{
- tickCount: 5,
- label: {
- formatter: (v: string) =>
- Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(v)),
- },
- grid: {
- line: {
- style: {
- stroke: axisColor,
- },
- },
- },
- }}
- tooltip={{
- formatter: (datum) => ({
- name: datum.category,
- value: Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(datum.x)),
- }),
- }}
- />
- );
-};
diff --git a/apps/dashboard/components/dashboard/confirm.tsx b/apps/dashboard/components/dashboard/confirm.tsx
deleted file mode 100644
index 3ad2c9fbfc..0000000000
--- a/apps/dashboard/components/dashboard/confirm.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-"use client";
-
-import { Button, DialogContainer } from "@unkey/ui";
-import type React from "react";
-import { useState } from "react";
-
-export type ConfirmProps = {
- title: string;
- description?: string;
- trigger: (onClick: () => void) => React.ReactNode;
- onConfirm: () => void | Promise;
- variant?: "destructive";
- disabled?: boolean;
-};
-
-export const Confirm: React.FC = (props): JSX.Element => {
- const [isOpen, setIsOpen] = useState(false);
-
- const [loading, setLoading] = useState(false);
-
- const onConfirm = async () => {
- setLoading(true);
- await props.onConfirm();
- setLoading(false);
- setIsOpen(false);
- };
-
- return (
- <>
- {props.trigger(() => setIsOpen(true))}
-
-
- Confirm
-
-
- }
- >
-
{props.description}
-
- >
- );
-};
diff --git a/apps/dashboard/components/dashboard/navbar.tsx b/apps/dashboard/components/dashboard/navbar.tsx
deleted file mode 100644
index a47b1936ad..0000000000
--- a/apps/dashboard/components/dashboard/navbar.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-"use client";
-
-import Link from "next/link";
-import * as React from "react";
-
-import { cn } from "@/lib/utils";
-import { Separator } from "@unkey/ui";
-import { useRouter, useSelectedLayoutSegment } from "next/navigation";
-
-type Props = {
- navigation: {
- label: string;
- href: string;
- segment: string | null;
- tag?: string;
- isActive?: boolean;
- }[];
- segment?: string;
- className?: string;
-};
-
-export const Navbar: React.FC
> = ({
- navigation,
- className,
- segment,
-}) => {
- return (
-
-
-
- {navigation.map(({ label, href, segment: _segment, tag }) => (
-
- ))}
-
-
-
-
- );
-};
-
-const NavItem: React.FC = ({ label, href, segment, tag, isActive }) => {
- const selectedSegment = useSelectedLayoutSegment();
- const [isPending, startTransition] = React.useTransition();
- const router = useRouter();
-
- const active = segment === selectedSegment || isActive;
-
- return (
-
-
- startTransition(() => {
- router.push(href);
- })
- }
- className={cn(
- "text-sm flex items-center gap-1 font-medium px-3 -mx-3 text-content-subtle hover:bg-background-subtle rounded-md hover:text-primary",
- {
- "text-primary": active,
- },
- )}
- >
- {label}
- {tag ? (
-
- {tag}
-
- ) : null}
-
-
- );
-};
diff --git a/apps/dashboard/components/dashboard/page-header.tsx b/apps/dashboard/components/dashboard/page-header.tsx
deleted file mode 100644
index da9c3d9582..0000000000
--- a/apps/dashboard/components/dashboard/page-header.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { cn } from "@/lib/utils";
-import type React from "react";
-
-type Props = {
- title: React.ReactNode;
- description?: string;
- /**
- * A set of components displayed in the top right
- * null components are filtered out
- */
- actions?: React.ReactNode[];
- /**
- * Additional classes to be applied to the root element
- */
- className?: string;
-};
-
-export const PageHeader: React.FC = ({ title, description, actions, className }) => {
- const actionRows: React.ReactNode[][] = [];
- if (actions) {
- for (let i = 0; i < actions.length; i += 3) {
- actionRows.push(actions.slice(i, i + 3));
- }
- }
-
- return (
-
-
-
{title}
-
{description}
-
- {actionRows.map((row, i) => (
-
- {row.map((action, i) => (
- // biome-ignore lint/suspicious/noArrayIndexKey: I got nothing better right now
- {action}
- ))}
-
- ))}
-
- );
-};
diff --git a/apps/dashboard/components/logs/llm-search/components/search-actions.tsx b/apps/dashboard/components/logs/llm-search/components/search-actions.tsx
deleted file mode 100644
index 0aa015464f..0000000000
--- a/apps/dashboard/components/logs/llm-search/components/search-actions.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { XMark } from "@unkey/icons";
-import { SearchExampleTooltip } from "./search-example-tooltip";
-
-type SearchActionsProps = {
- exampleQueries?: string[];
- searchText: string;
- hideClear: boolean;
- hideExplainer: boolean;
- isProcessing: boolean;
- searchMode: "allowTypeDuringSearch" | "debounced" | "manual";
- onClear: () => void;
- onSelectExample: (query: string) => void;
-};
-
-/**
- * SearchActions component renders the right-side actions (clear button or examples tooltip)
- */
-export const SearchActions: React.FC = ({
- exampleQueries,
- searchText,
- hideClear,
- hideExplainer,
- isProcessing,
- searchMode,
- onClear,
- onSelectExample,
-}) => {
- // Don't render anything if processing (unless in allowTypeDuringSearch mode)
- if (!(!isProcessing || searchMode === "allowTypeDuringSearch")) {
- return null;
- }
-
- // Render clear button when there's text
- if (searchText.length > 0 && !hideClear) {
- return (
-
-
-
- );
- }
-
- if (searchText.length === 0 && !hideExplainer) {
- return (
-
- );
- }
-
- return null;
-};
diff --git a/apps/dashboard/components/logs/llm-search/components/search-example-tooltip.tsx b/apps/dashboard/components/logs/llm-search/components/search-example-tooltip.tsx
deleted file mode 100644
index 56e23caf33..0000000000
--- a/apps/dashboard/components/logs/llm-search/components/search-example-tooltip.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CaretRightOutline, CircleInfoSparkle } from "@unkey/icons";
-import { InfoTooltip } from "@unkey/ui";
-
-type SearchExampleTooltipProps = {
- onSelectExample: (query: string) => void;
- exampleQueries?: string[];
-};
-
-export const SearchExampleTooltip: React.FC = ({
- onSelectExample,
- exampleQueries,
-}) => {
- const examples = exampleQueries ?? [
- "Show failed requests today",
- "auth errors in the last 3h",
- "API calls from a path that includes /api/v1/oz",
- ];
-
- return (
-
-
- Try queries like:
- (click to use)
-
-
- {examples.map((example) => (
-
-
- onSelectExample(example)}
- data-testid={`example-${example}`}
- >
- "{example}"
-
-
- ))}
-
-
- }
- delayDuration={150}
- >
-
-
-
-
- );
-};
diff --git a/apps/dashboard/components/logs/llm-search/components/search-icon.tsx b/apps/dashboard/components/logs/llm-search/components/search-icon.tsx
deleted file mode 100644
index 3bd93ec221..0000000000
--- a/apps/dashboard/components/logs/llm-search/components/search-icon.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Magnifier, Refresh3 } from "@unkey/icons";
-
-type SearchIconProps = {
- isProcessing: boolean;
-};
-
-export const SearchIcon = ({ isProcessing }: SearchIconProps) => {
- if (isProcessing) {
- return
;
- }
-
- return
;
-};
diff --git a/apps/dashboard/components/logs/llm-search/components/search-input.tsx b/apps/dashboard/components/logs/llm-search/components/search-input.tsx
deleted file mode 100644
index 6d47accfdc..0000000000
--- a/apps/dashboard/components/logs/llm-search/components/search-input.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-type SearchInputProps = {
- value: string;
- placeholder: string;
- isProcessing: boolean;
- isLoading: boolean;
- loadingText: string;
- clearingText: string;
- searchMode: "allowTypeDuringSearch" | "debounced" | "manual";
- onChange: (e: React.ChangeEvent
) => void;
- onKeyDown: (e: React.KeyboardEvent) => void;
- inputRef: React.RefObject;
-};
-
-const LLM_LIMITS_MAX_QUERY_LENGTH = 120;
-export const SearchInput = ({
- value,
- placeholder,
- isProcessing,
- isLoading,
- loadingText,
- clearingText,
- searchMode,
- onChange,
- onKeyDown,
- inputRef,
-}: SearchInputProps) => {
- // Show loading state unless we're in allowTypeDuringSearch mode
- if (isProcessing && searchMode !== "allowTypeDuringSearch") {
- return (
-
- {isLoading ? loadingText : clearingText}
-
- );
- }
-
- return (
-
- );
-};
diff --git a/apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.test.tsx b/apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.test.tsx
deleted file mode 100644
index 07501d8098..0000000000
--- a/apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.test.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import { act, renderHook } from "@testing-library/react-hooks";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { useSearchStrategy } from "./use-search-strategy";
-
-describe("useSearchStrategy", () => {
- // Mock timers for debounce/throttle testing
- beforeEach(() => {
- vi.useFakeTimers();
- });
-
- afterEach(() => {
- vi.restoreAllMocks();
- });
-
- const onSearchMock = vi.fn();
-
- it("should execute search immediately with executeSearch", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- act(() => {
- result.current.executeSearch("test query");
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
- expect(onSearchMock).toHaveBeenCalledWith("test query");
- });
-
- it("should not execute search with empty query", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- act(() => {
- result.current.executeSearch(" ");
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
- });
-
- it("should debounce search calls with debouncedSearch", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- act(() => {
- result.current.debouncedSearch("test query");
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
-
- act(() => {
- vi.advanceTimersByTime(499);
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
-
- act(() => {
- vi.advanceTimersByTime(1);
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
- expect(onSearchMock).toHaveBeenCalledWith("test query");
- });
-
- it("should cancel previous debounce if debouncedSearch is called again", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- act(() => {
- result.current.debouncedSearch("first query");
- });
-
- act(() => {
- vi.advanceTimersByTime(300);
- });
-
- act(() => {
- result.current.debouncedSearch("second query");
- });
-
- act(() => {
- vi.advanceTimersByTime(300);
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
-
- act(() => {
- vi.advanceTimersByTime(200);
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
- expect(onSearchMock).toHaveBeenCalledWith("second query");
- expect(onSearchMock).not.toHaveBeenCalledWith("first query");
- });
-
- it("should use debounce for initial query with throttledSearch", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- act(() => {
- result.current.throttledSearch("initial query");
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
-
- act(() => {
- vi.advanceTimersByTime(500);
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
- expect(onSearchMock).toHaveBeenCalledWith("initial query");
- });
-
- it("should throttle subsequent searches", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- // First search - should be debounced
- act(() => {
- result.current.throttledSearch("initial query");
- vi.advanceTimersByTime(500);
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
-
- // Reset mock to track subsequent calls
- onSearchMock.mockReset();
-
- // Second search immediately after - should be throttled
- act(() => {
- result.current.throttledSearch("second query");
- });
-
- // Should not execute immediately due to throttling
- expect(onSearchMock).not.toHaveBeenCalled();
-
- // Advance time to just before throttle interval ends
- act(() => {
- vi.advanceTimersByTime(999);
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
-
- // Complete the throttle interval
- act(() => {
- vi.advanceTimersByTime(1);
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
- expect(onSearchMock).toHaveBeenCalledWith("second query");
- });
-
- it("should clean up timers with clearDebounceTimer", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- act(() => {
- result.current.debouncedSearch("test query");
- });
-
- act(() => {
- result.current.clearDebounceTimer();
- });
-
- act(() => {
- vi.advanceTimersByTime(1000);
- });
-
- expect(onSearchMock).not.toHaveBeenCalled();
- });
-
- it("should reset search state with resetSearchState", () => {
- const { result } = renderHook(() => useSearchStrategy(onSearchMock, 500));
-
- // First search to set initial state
- act(() => {
- result.current.throttledSearch("initial query");
- vi.advanceTimersByTime(500);
- });
-
- onSearchMock.mockReset();
-
- // Reset search state
- act(() => {
- result.current.resetSearchState();
- });
-
- // Next search should be debounced again, not throttled
- act(() => {
- result.current.throttledSearch("new query after reset");
- });
-
- // Should not execute immediately (debounced, not throttled)
- expect(onSearchMock).not.toHaveBeenCalled();
-
- act(() => {
- vi.advanceTimersByTime(500);
- });
-
- expect(onSearchMock).toHaveBeenCalledTimes(1);
- expect(onSearchMock).toHaveBeenCalledWith("new query after reset");
- });
-});
diff --git a/apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.ts b/apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.ts
deleted file mode 100644
index c44ff130a4..0000000000
--- a/apps/dashboard/components/logs/llm-search/hooks/use-search-strategy.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { useCallback, useRef } from "react";
-
-/**
- * Custom hook that provides different search strategies
- * @param onSearch Function to execute the search
- * @param debounceTime Delay for debounce in ms
- */
-export const useSearchStrategy = (onSearch: (query: string) => void, debounceTime = 500) => {
- const debounceTimerRef = useRef(null);
- const lastSearchTimeRef = useRef(0);
- const THROTTLE_INTERVAL = 1000;
-
- /**
- * Clears the debounce timer
- */
- const clearDebounceTimer = useCallback(() => {
- if (debounceTimerRef.current) {
- clearTimeout(debounceTimerRef.current);
- debounceTimerRef.current = null;
- }
- }, []);
-
- /**
- * Executes the search with the given query
- */
- const executeSearch = useCallback(
- (query: string) => {
- if (query.trim()) {
- try {
- lastSearchTimeRef.current = Date.now();
- onSearch(query.trim());
- } catch (error) {
- console.error("Search failed:", error);
- }
- }
- },
- [onSearch],
- );
-
- /**
- * Debounced search - waits for user to stop typing before executing search
- */
- const debouncedSearch = useCallback(
- (search: string) => {
- clearDebounceTimer();
-
- debounceTimerRef.current = setTimeout(() => {
- executeSearch(search);
- }, debounceTime);
- },
- [clearDebounceTimer, executeSearch, debounceTime],
- );
-
- /**
- * Throttled search with initial debounce - debounce first query, throttle subsequent searches
- */
-
- const throttledSearch = useCallback(
- (search: string) => {
- const now = Date.now();
- const timeElapsed = now - lastSearchTimeRef.current;
- const query = search.trim();
-
- // If this is the first search, use debounced search
- if (lastSearchTimeRef.current === 0 && query) {
- debouncedSearch(search);
- return;
- }
-
- // For subsequent searches, use throttling
- if (timeElapsed >= THROTTLE_INTERVAL) {
- // Enough time has passed, execute immediately
- executeSearch(search);
- } else if (query) {
- // Not enough time has passed, schedule for later
- clearDebounceTimer();
-
- // Schedule execution after remaining throttle time
- const remainingTime = THROTTLE_INTERVAL - timeElapsed;
- debounceTimerRef.current = setTimeout(() => {
- throttledSearch(search);
- }, remainingTime);
- }
- },
- [clearDebounceTimer, debouncedSearch, executeSearch],
- );
-
- /**
- * Resets search state for new search sequences
- */
- const resetSearchState = useCallback(() => {
- lastSearchTimeRef.current = 0;
- }, []);
-
- return {
- debouncedSearch,
- throttledSearch,
- executeSearch,
- clearDebounceTimer,
- resetSearchState,
- };
-};
diff --git a/apps/dashboard/components/logs/llm-search/index.tsx b/apps/dashboard/components/logs/llm-search/index.tsx
deleted file mode 100644
index 30d6b2844c..0000000000
--- a/apps/dashboard/components/logs/llm-search/index.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut";
-import { cn } from "@/lib/utils";
-import { useEffect, useRef, useState } from "react";
-import { SearchActions } from "./components/search-actions";
-import { SearchIcon } from "./components/search-icon";
-import { SearchInput } from "./components/search-input";
-import { useSearchStrategy } from "./hooks/use-search-strategy";
-
-type SearchMode = "allowTypeDuringSearch" | "debounced" | "manual";
-
-type Props = {
- exampleQueries?: string[];
- onSearch: (query: string) => void;
- onClear?: () => void;
- placeholder?: string;
- isLoading: boolean;
- hideExplainer?: boolean;
- hideClear?: boolean;
- loadingText?: string;
- clearingText?: string;
- searchMode?: SearchMode;
- debounceTime?: number;
-};
-
-export const LogsLLMSearch = ({
- exampleQueries,
- onSearch,
- isLoading,
- onClear,
- hideExplainer = false,
- hideClear = false,
- placeholder = "Search and filter with AI…",
- loadingText = "AI consults the Palantír...",
- clearingText = "Clearing search...",
- searchMode = "manual",
- debounceTime = 500,
-}: Props) => {
- const [searchText, setSearchText] = useState("");
- const [isClearingState, setIsClearingState] = useState(false);
-
- const inputRef = useRef(null);
-
- const isClearing = isClearingState;
- const isProcessing = isLoading || isClearing;
-
- const { debouncedSearch, throttledSearch, executeSearch, clearDebounceTimer, resetSearchState } =
- useSearchStrategy(onSearch, debounceTime);
- useKeyboardShortcut("s", () => {
- inputRef.current?.click();
- inputRef.current?.focus();
- });
-
- const handleClear = () => {
- clearDebounceTimer();
- setIsClearingState(true);
-
- setTimeout(() => {
- onClear?.();
- setSearchText("");
- }, 0);
-
- setIsClearingState(false);
- resetSearchState();
- };
-
- const handleInputChange = (e: React.ChangeEvent) => {
- const value = e.target.value;
- const wasFilled = searchText !== "";
-
- setSearchText(value);
-
- // Handle clearing
- if (wasFilled && value === "") {
- handleClear();
- return;
- }
-
- // Skip if empty
- if (value === "") {
- return;
- }
-
- // Apply appropriate search strategy based on mode
- switch (searchMode) {
- case "allowTypeDuringSearch":
- throttledSearch(value);
- break;
- case "debounced":
- debouncedSearch(value);
- break;
- case "manual":
- // Do nothing - search triggered on Enter key or preset click
- break;
- }
- };
-
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === "Escape") {
- e.preventDefault();
- setSearchText("");
- handleClear();
- inputRef.current?.blur();
- }
-
- if (e.key === "Enter") {
- e.preventDefault();
- if (searchText !== "") {
- executeSearch(searchText);
- } else {
- handleClear();
- }
- }
- };
-
- const handlePresetQuery = (query: string) => {
- setSearchText(query);
- executeSearch(query);
- };
-
- // Clean up timers on unmount
- // biome-ignore lint/correctness/useExhaustiveDependencies:
- useEffect(() => {
- return clearDebounceTimer();
- }, []);
-
- return (
-
-
0 ? "bg-gray-4" : "",
- isProcessing ? "bg-gray-4" : "",
- )}
- >
-
-
-
-
-
- );
-};
diff --git a/apps/dashboard/components/ui/calendar.tsx b/apps/dashboard/components/ui/calendar.tsx
deleted file mode 100644
index 43da2d2ccb..0000000000
--- a/apps/dashboard/components/ui/calendar.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-"use client";
-
-import { ChevronLeft, ChevronRight } from "@unkey/icons";
-import type * as React from "react";
-import { DayPicker } from "react-day-picker";
-
-import { cn } from "@/lib/utils";
-import { buttonVariants } from "@unkey/ui";
-
-export type CalendarProps = React.ComponentProps;
-
-function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
- return (
- ,
- IconRight: () => ,
- }}
- {...props}
- />
- );
-}
-Calendar.displayName = "Calendar";
-
-export { Calendar };
diff --git a/apps/dashboard/components/ui/code.tsx b/apps/dashboard/components/ui/code.tsx
deleted file mode 100644
index 2aa5c678ac..0000000000
--- a/apps/dashboard/components/ui/code.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { type VariantProps, cva } from "class-variance-authority";
-import type * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-const codeVariants = cva(
- "inline-flex font-mono items-center rounded-md border border-border bg-transparent px-2.5 py-2 text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
- {
- variants: {
- variant: {
- default: " text-primary bg-background-subtle hover:border-primary",
-
- outline: "text-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-export interface CodeProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-function Code({ className, variant, ...props }: CodeProps) {
- return ;
-}
-
-export { Code, codeVariants };
diff --git a/apps/dashboard/components/ui/metric.tsx b/apps/dashboard/components/ui/metric.tsx
deleted file mode 100644
index 73378a8d73..0000000000
--- a/apps/dashboard/components/ui/metric.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { cn } from "@/lib/utils";
-import type * as React from "react";
-
-interface MetricProps {
- label: string;
- value: string | React.ReactNode;
- className?: string;
-}
-
-const Metric: React.FC = ({ label, value, className }) => {
- return (
-
- );
-};
-
-export { Metric };
diff --git a/apps/dashboard/components/ui/shiny-text.tsx b/apps/dashboard/components/ui/shiny-text.tsx
deleted file mode 100644
index b3cf74a18b..0000000000
--- a/apps/dashboard/components/ui/shiny-text.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { CSSProperties, ComponentPropsWithoutRef, FC } from "react";
-
-import { cn } from "@/lib/utils";
-
-export interface AnimatedShinyTextProps extends ComponentPropsWithoutRef<"span"> {
- shimmerWidth?: number;
- textColor?: string;
- gradientColor?: string;
-}
-
-export const AnimatedShinyText: FC = ({
- children,
- className,
- shimmerWidth = 100,
- textColor,
- gradientColor,
- ...props
-}) => {
- return (
-
- {children}
-
- );
-};