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