mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
refactor(ui): replace HeroUI Snippet with CodeSnippet component (#10319)
This commit is contained in:
@@ -14,9 +14,9 @@ import { createSamlConfig, updateSamlConfig } from "@/actions/integrations";
|
||||
import { AddIcon } from "@/components/icons";
|
||||
import { Button, Card, CardContent, CardHeader } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { CustomServerInput } from "@/components/ui/custom";
|
||||
import { CustomLink } from "@/components/ui/custom/custom-link";
|
||||
import { SnippetChip } from "@/components/ui/entities";
|
||||
import { FormButtons } from "@/components/ui/form";
|
||||
import { apiBaseUrl } from "@/lib";
|
||||
|
||||
@@ -258,7 +258,11 @@ export const SamlConfigForm = ({
|
||||
: `${apiBaseUrl}/accounts/saml/your-domain.com/acs/`;
|
||||
|
||||
return (
|
||||
<form ref={formRef} action={formAction} className="flex flex-col gap-2">
|
||||
<form
|
||||
ref={formRef}
|
||||
action={formAction}
|
||||
className="flex min-w-0 flex-col gap-2"
|
||||
>
|
||||
<div className="py-1 text-xs">
|
||||
Need help configuring SAML SSO?{" "}
|
||||
<CustomLink
|
||||
@@ -304,9 +308,9 @@ export const SamlConfigForm = ({
|
||||
<span className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
ACS URL:
|
||||
</span>
|
||||
<SnippetChip
|
||||
<CodeSnippet
|
||||
value={acsUrl}
|
||||
ariaLabel="Copy ACS URL to clipboard"
|
||||
ariaLabel="Copy ACS URL"
|
||||
className="h-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
@@ -315,9 +319,9 @@ export const SamlConfigForm = ({
|
||||
<span className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Audience:
|
||||
</span>
|
||||
<SnippetChip
|
||||
<CodeSnippet
|
||||
value="urn:prowler.com:sp"
|
||||
ariaLabel="Copy Audience to clipboard"
|
||||
ariaLabel="Copy Audience"
|
||||
className="h-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
import Link from "next/link";
|
||||
|
||||
import { AddIcon } from "../icons";
|
||||
import { Button, Card, CardContent, CardHeader } from "../shadcn";
|
||||
import { Separator } from "../shadcn/separator/separator";
|
||||
import { CodeSnippet } from "../ui/code-snippet/code-snippet";
|
||||
import { DateWithTime } from "../ui/entities";
|
||||
|
||||
interface InvitationDetailsProps {
|
||||
@@ -89,20 +89,7 @@ export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => {
|
||||
Share this link with the user:
|
||||
</h3>
|
||||
|
||||
<div className="flex w-full flex-col items-start justify-between overflow-hidden">
|
||||
<Snippet
|
||||
classNames={{
|
||||
base: "w-full max-w-full",
|
||||
content: "min-w-0 overflow-hidden",
|
||||
pre: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap",
|
||||
}}
|
||||
hideSymbol
|
||||
variant="bordered"
|
||||
className="bg-bg-neutral-secondary max-w-full overflow-hidden py-1"
|
||||
>
|
||||
<p className="min-w-0 truncate text-sm">{invitationLink}</p>
|
||||
</Snippet>
|
||||
</div>
|
||||
<CodeSnippet value={invitationLink} className="max-w-full" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex w-full items-center justify-end">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { DateWithTime, EntityInfo, InfoField } from "@/components/ui/entities";
|
||||
import { StatusBadge } from "@/components/ui/table/status-badge";
|
||||
import { ProviderProps, ProviderType, ScanProps, TaskDetails } from "@/types";
|
||||
@@ -81,18 +80,17 @@ export const ScanDetail = ({
|
||||
</div>
|
||||
|
||||
<InfoField label="Scan ID" variant="simple">
|
||||
<Snippet hideSymbol>{scanDetails.id}</Snippet>
|
||||
<CodeSnippet value={scanDetails.id} />
|
||||
</InfoField>
|
||||
|
||||
{scan.state === "failed" && taskDetails?.attributes.result && (
|
||||
<>
|
||||
{taskDetails.attributes.result.exc_message && (
|
||||
<InfoField label="Error Message" variant="simple">
|
||||
<Snippet hideSymbol>
|
||||
<span className="text-xs whitespace-pre-line">
|
||||
{taskDetails.attributes.result.exc_message.join("\n")}
|
||||
</span>
|
||||
</Snippet>
|
||||
<CodeSnippet
|
||||
value={taskDetails.attributes.result.exc_message.join("\n")}
|
||||
multiline
|
||||
/>
|
||||
</InfoField>
|
||||
)}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
|
||||
@@ -21,6 +21,10 @@ interface CodeSnippetProps {
|
||||
icon?: ReactNode;
|
||||
/** Function to format the displayed text (value is still copied as-is) */
|
||||
formatter?: (value: string) => string;
|
||||
/** Enable multiline display (disables truncation, enables word wrap) */
|
||||
multiline?: boolean;
|
||||
/** Custom aria-label for the copy button */
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export const CodeSnippet = ({
|
||||
@@ -30,6 +34,8 @@ export const CodeSnippet = ({
|
||||
hideCopyButton = false,
|
||||
icon,
|
||||
formatter,
|
||||
multiline = false,
|
||||
ariaLabel = "Copy to clipboard",
|
||||
}: CodeSnippetProps) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
@@ -46,7 +52,7 @@ export const CodeSnippet = ({
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="text-text-neutral-secondary hover:text-text-neutral-primary shrink-0 cursor-pointer transition-colors"
|
||||
aria-label="Copy to clipboard"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
@@ -66,7 +72,7 @@ export const CodeSnippet = ({
|
||||
"hover:bg-bg-neutral-tertiary text-text-neutral-secondary hover:text-text-neutral-primary shrink-0 cursor-pointer rounded-md p-1 transition-colors",
|
||||
className,
|
||||
)}
|
||||
aria-label="Copy to clipboard"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
@@ -80,19 +86,26 @@ export const CodeSnippet = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-bg-neutral-tertiary text-text-neutral-primary border-border-neutral-tertiary flex h-6 w-fit min-w-0 items-center gap-1.5 rounded-full border-2 px-2 py-0.5 text-xs",
|
||||
"bg-bg-neutral-tertiary text-text-neutral-primary border-border-neutral-tertiary flex w-fit min-w-0 items-center gap-1.5 border-2 px-2 py-0.5 text-xs",
|
||||
multiline ? "h-auto rounded-lg" : "h-6 rounded-full",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{icon && (
|
||||
<span className="text-text-neutral-secondary shrink-0">{icon}</span>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<code className="min-w-0 flex-1 truncate">{displayValue}</code>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">{value}</TooltipContent>
|
||||
</Tooltip>
|
||||
{multiline ? (
|
||||
<span className="min-w-0 flex-1 break-all whitespace-pre-wrap">
|
||||
{displayValue}
|
||||
</span>
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="min-w-0 flex-1 truncate">{displayValue}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">{value}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!hideCopyButton && <CopyButton />}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,4 +3,3 @@ export * from "./entity-info";
|
||||
export * from "./get-provider-logo";
|
||||
export * from "./info-field";
|
||||
export * from "./scan-status";
|
||||
export * from "./snippet-chip";
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
import { cn } from "@heroui/theme";
|
||||
import { Tooltip } from "@heroui/tooltip";
|
||||
import React from "react";
|
||||
|
||||
import { CopyIcon, DoneIcon } from "@/components/icons";
|
||||
|
||||
interface SnippetChipProps {
|
||||
value: string;
|
||||
ariaLabel?: string;
|
||||
icon?: React.ReactNode;
|
||||
hideCopyButton?: boolean;
|
||||
formatter?: (value: string) => string;
|
||||
className?: string;
|
||||
}
|
||||
export const SnippetChip = ({
|
||||
value,
|
||||
hideCopyButton = false,
|
||||
ariaLabel = `Copy ${value} to clipboard`,
|
||||
icon,
|
||||
formatter,
|
||||
className,
|
||||
...props
|
||||
}: SnippetChipProps) => {
|
||||
return (
|
||||
<Snippet
|
||||
className={cn("h-6", className)}
|
||||
classNames={{
|
||||
content: "min-w-0 overflow-hidden",
|
||||
pre: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap",
|
||||
base: "border-border-neutral-tertiary bg-bg-neutral-tertiary rounded-lg border py-1",
|
||||
}}
|
||||
size="sm"
|
||||
hideSymbol
|
||||
copyIcon={<CopyIcon size={16} />}
|
||||
checkIcon={<DoneIcon size={16} />}
|
||||
hideCopyButton={hideCopyButton}
|
||||
codeString={value}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-2" aria-label={ariaLabel}>
|
||||
{icon}
|
||||
<Tooltip content={value} placement="top" size="sm">
|
||||
<span className="min-w-0 flex-1 truncate text-xs">
|
||||
{formatter ? formatter(value) : value}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Snippet>
|
||||
);
|
||||
};
|
||||
@@ -191,7 +191,7 @@ export function DataTablePagination({
|
||||
|
||||
{/* Page info and navigation */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-text-neutral-secondary text-xs font-medium">
|
||||
<span className="text-text-neutral-secondary hidden text-xs font-medium sm:inline">
|
||||
Page {currentPage} of {totalPages}
|
||||
</span>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Modal } from "@/components/shadcn/modal";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert/Alert";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
|
||||
interface ApiKeySuccessModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -36,18 +35,12 @@ export const ApiKeySuccessModal = ({
|
||||
<p className="text-text-neutral-primary text-sm font-medium">
|
||||
Your API Key
|
||||
</p>
|
||||
<Snippet
|
||||
hideSymbol
|
||||
classNames={{
|
||||
pre: "font-mono text-sm break-all whitespace-pre-wrap p-2 text-text-neutral-primary",
|
||||
}}
|
||||
tooltipProps={{
|
||||
content: "Copy API key",
|
||||
color: "default",
|
||||
}}
|
||||
>
|
||||
{apiKey}
|
||||
</Snippet>
|
||||
<CodeSnippet
|
||||
value={apiKey}
|
||||
multiline
|
||||
ariaLabel="Copy API key"
|
||||
className="w-full px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ export const MembershipItem = ({
|
||||
setIsOpen={setIsEditOpen}
|
||||
/>
|
||||
</Modal>
|
||||
<Card variant="inner" className="min-w-[320px] p-2">
|
||||
<div className="flex w-full items-center gap-4">
|
||||
<Card variant="inner" className="p-2">
|
||||
<div className="flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
|
||||
<Chip size="sm" variant="flat" color="secondary">
|
||||
{membership.attributes.role}
|
||||
</Chip>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
import { Trash2Icon } from "lucide-react";
|
||||
|
||||
import { revokeApiKey } from "@/actions/api-keys/api-keys";
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/components/ui/alert/Alert";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { ModalButtons } from "@/components/ui/custom/custom-modal-buttons";
|
||||
|
||||
import { FALLBACK_VALUES } from "./api-keys/constants";
|
||||
@@ -67,16 +67,12 @@ export const RevokeApiKeyModal = ({
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>Are you sure you want to revoke this API key?</p>
|
||||
|
||||
<Snippet
|
||||
hideSymbol
|
||||
hideCopyButton={true}
|
||||
classNames={{
|
||||
pre: "font-mono text-sm break-all whitespace-pre-wrap",
|
||||
}}
|
||||
>
|
||||
<p>{apiKey?.attributes.name || FALLBACK_VALUES.UNNAMED_KEY}</p>
|
||||
<p className="mt-1 text-xs">Prefix: {apiKey?.attributes.prefix}</p>
|
||||
</Snippet>
|
||||
<CodeSnippet
|
||||
value={`${apiKey?.attributes.name || FALLBACK_VALUES.UNNAMED_KEY}\nPrefix: ${apiKey?.attributes.prefix}`}
|
||||
hideCopyButton
|
||||
multiline
|
||||
className="w-full px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -68,6 +68,7 @@ export const RoleItem = ({
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="px-0"
|
||||
>
|
||||
{isExpanded ? "Hide details" : "Show details"}
|
||||
</Button>
|
||||
|
||||
@@ -4,13 +4,14 @@ import { Divider } from "@heroui/divider";
|
||||
|
||||
import { ProwlerShort } from "@/components/icons";
|
||||
import { Card, CardContent } from "@/components/shadcn";
|
||||
import { DateWithTime, InfoField, SnippetChip } from "@/components/ui/entities";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { DateWithTime, InfoField } from "@/components/ui/entities";
|
||||
import { UserDataWithRoles } from "@/types/users";
|
||||
|
||||
const TenantIdCopy = ({ id }: { id: string }) => {
|
||||
return (
|
||||
<div className="flex max-w-full min-w-0 items-center gap-2 md:flex-col md:items-start md:justify-start">
|
||||
<SnippetChip value={id} className="max-w-full" />
|
||||
<CodeSnippet value={id} className="max-w-full" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user