mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3352 from Infisical/feat/addPasswordGenerator
Add new PasswordGenerator and Slider components
This commit is contained in:
@@ -0,0 +1,273 @@
|
|||||||
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { faCheck, faCopy, faKey, faRefresh } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Button, Checkbox, IconButton, Slider } from "@app/components/v2";
|
||||||
|
import { useTimedReset } from "@app/hooks";
|
||||||
|
|
||||||
|
type PasswordOptionsType = {
|
||||||
|
length: number;
|
||||||
|
useUppercase: boolean;
|
||||||
|
useLowercase: boolean;
|
||||||
|
useNumbers: boolean;
|
||||||
|
useSpecialChars: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PasswordGeneratorModalProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onUsePassword?: (password: string) => void;
|
||||||
|
minLength?: number;
|
||||||
|
maxLength?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PasswordGeneratorModal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onUsePassword,
|
||||||
|
minLength = 12,
|
||||||
|
maxLength = 64
|
||||||
|
}: PasswordGeneratorModalProps) => {
|
||||||
|
const [copyText, isCopying, setCopyText] = useTimedReset<string>({
|
||||||
|
initialState: "Copy"
|
||||||
|
});
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [passwordOptions, setPasswordOptions] = useState<PasswordOptionsType>({
|
||||||
|
length: minLength,
|
||||||
|
useUppercase: true,
|
||||||
|
useLowercase: true,
|
||||||
|
useNumbers: true,
|
||||||
|
useSpecialChars: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const generatePassword = () => {
|
||||||
|
const charset = {
|
||||||
|
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||||
|
numbers: "0123456789",
|
||||||
|
specialChars: "-_.~!*"
|
||||||
|
};
|
||||||
|
|
||||||
|
let availableChars = "";
|
||||||
|
if (passwordOptions.useUppercase) availableChars += charset.uppercase;
|
||||||
|
if (passwordOptions.useLowercase) availableChars += charset.lowercase;
|
||||||
|
if (passwordOptions.useNumbers) availableChars += charset.numbers;
|
||||||
|
if (passwordOptions.useSpecialChars) availableChars += charset.specialChars;
|
||||||
|
|
||||||
|
if (availableChars === "") availableChars = charset.lowercase + charset.numbers;
|
||||||
|
|
||||||
|
let newPassword = "";
|
||||||
|
for (let i = 0; i < passwordOptions.length; i += 1) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * availableChars.length);
|
||||||
|
newPassword += availableChars[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPassword;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
}, [isOpen, onClose]);
|
||||||
|
|
||||||
|
const password = useMemo(() => {
|
||||||
|
return generatePassword();
|
||||||
|
}, [passwordOptions, refresh]);
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(password)
|
||||||
|
.then(() => {
|
||||||
|
setCopyText("Copied");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setCopyText("Copy failed");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const usePassword = () => {
|
||||||
|
if (onUsePassword) {
|
||||||
|
onUsePassword(password);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
|
||||||
|
<div
|
||||||
|
ref={modalRef}
|
||||||
|
className="w-full max-w-lg rounded-lg border border-mineshaft-600 bg-mineshaft-800 shadow-xl"
|
||||||
|
>
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="mb-1 text-xl font-semibold text-bunker-200">Password Generator</h2>
|
||||||
|
<p className="mb-6 text-sm text-bunker-400">Generate strong unique passwords</p>
|
||||||
|
|
||||||
|
<div className="relative mb-4 rounded-md bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="w-4/5 select-all break-all pr-2 font-mono text-lg">{password}</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => setRefresh((prev) => !prev)}
|
||||||
|
className="w-full text-bunker-300 hover:text-bunker-100"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faRefresh} className="mr-1 h-3 w-3" />
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
className="w-full text-bunker-300 hover:text-bunker-100"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopying ? faCheck : faCopy} className="mr-1 h-3 w-3" />
|
||||||
|
{copyText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="mb-1 flex items-center justify-between">
|
||||||
|
<label htmlFor="password-length" className="text-sm text-bunker-300">
|
||||||
|
Password length: {passwordOptions.length}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
id="password-length"
|
||||||
|
min={minLength}
|
||||||
|
max={maxLength}
|
||||||
|
value={passwordOptions.length}
|
||||||
|
onChange={(value) => setPasswordOptions({ ...passwordOptions, length: value })}
|
||||||
|
className="mb-1"
|
||||||
|
aria-labelledby="password-length-label"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6 flex flex-row justify-between gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id="useUppercase"
|
||||||
|
className="mr-2 data-[state=checked]:bg-primary"
|
||||||
|
isChecked={passwordOptions.useUppercase}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setPasswordOptions({ ...passwordOptions, useUppercase: checked as boolean })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
A-Z
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
id="useLowercase"
|
||||||
|
className="mr-2 data-[state=checked]:bg-primary"
|
||||||
|
isChecked={passwordOptions.useLowercase}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setPasswordOptions({ ...passwordOptions, useLowercase: checked as boolean })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
a-z
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
id="useNumbers"
|
||||||
|
className="mr-2 data-[state=checked]:bg-primary"
|
||||||
|
isChecked={passwordOptions.useNumbers}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setPasswordOptions({ ...passwordOptions, useNumbers: checked as boolean })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
0-9
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
id="useSpecialChars"
|
||||||
|
className="mr-2 data-[state=checked]:bg-primary"
|
||||||
|
isChecked={passwordOptions.useSpecialChars}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setPasswordOptions({ ...passwordOptions, useSpecialChars: checked as boolean })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
-_.~!*
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button size="sm" colorSchema="primary" variant="outline_bg" onClick={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
{onUsePassword && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={usePassword}
|
||||||
|
className="ml-2"
|
||||||
|
>
|
||||||
|
Use Password
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PasswordGeneratorProps = {
|
||||||
|
onUsePassword?: (password: string) => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
minLength?: number;
|
||||||
|
maxLength?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PasswordGenerator = ({
|
||||||
|
onUsePassword,
|
||||||
|
isDisabled = false,
|
||||||
|
minLength = 12,
|
||||||
|
maxLength = 64
|
||||||
|
}: PasswordGeneratorProps) => {
|
||||||
|
const [showGenerator, setShowGenerator] = useState(false);
|
||||||
|
|
||||||
|
const toggleGenerator = () => {
|
||||||
|
setShowGenerator(!showGenerator);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="generate password"
|
||||||
|
colorSchema="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={toggleGenerator}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
className="rounded text-bunker-400 transition-colors duration-150 hover:bg-mineshaft-700 hover:text-bunker-200"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faKey} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<PasswordGeneratorModal
|
||||||
|
isOpen={showGenerator}
|
||||||
|
onClose={() => setShowGenerator(false)}
|
||||||
|
onUsePassword={onUsePassword}
|
||||||
|
minLength={minLength}
|
||||||
|
maxLength={maxLength}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
2
frontend/src/components/v2/PasswordGenerator/index.tsx
Normal file
2
frontend/src/components/v2/PasswordGenerator/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export type { PasswordGeneratorProps } from "./PasswordGenerator";
|
||||||
|
export { PasswordGenerator } from "./PasswordGenerator";
|
||||||
237
frontend/src/components/v2/Slider/Slider.tsx
Normal file
237
frontend/src/components/v2/Slider/Slider.tsx
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
InputHTMLAttributes,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from "react";
|
||||||
|
import { cva, VariantProps } from "cva";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step?: number;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isRequired?: boolean;
|
||||||
|
showValue?: boolean;
|
||||||
|
valuePosition?: "top" | "right";
|
||||||
|
containerClassName?: string;
|
||||||
|
trackClassName?: string;
|
||||||
|
fillClassName?: string;
|
||||||
|
thumbClassName?: string;
|
||||||
|
onChange?: (value: number) => void;
|
||||||
|
onChangeComplete?: (value: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sliderTrackVariants = cva("h-1 w-full bg-mineshaft-600 rounded-full relative", {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "",
|
||||||
|
thin: "h-0.5",
|
||||||
|
thick: "h-1.5"
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
true: "opacity-50 cursor-not-allowed",
|
||||||
|
false: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sliderFillVariants = cva("absolute h-full rounded-full", {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary-500",
|
||||||
|
secondary: "bg-secondary-500",
|
||||||
|
danger: "bg-red-500"
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
true: "opacity-50",
|
||||||
|
false: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sliderThumbVariants = cva(
|
||||||
|
"absolute w-4 h-4 rounded-full shadow transform -translate-x-1/2 -mt-1.5 focus:outline-none",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary-500 focus:ring-2 focus:ring-primary-400/50",
|
||||||
|
secondary: "bg-secondary-500 focus:ring-2 focus:ring-secondary-400/50",
|
||||||
|
danger: "bg-red-500 focus:ring-2 focus:ring-red-400/50"
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
true: "opacity-50 cursor-not-allowed",
|
||||||
|
false: "cursor-pointer"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
sm: "w-3 h-3 -mt-1",
|
||||||
|
md: "w-4 h-4 -mt-1.5",
|
||||||
|
lg: "w-5 h-5 -mt-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const sliderContainerVariants = cva("relative inline-flex font-inter", {
|
||||||
|
variants: {
|
||||||
|
isFullWidth: {
|
||||||
|
true: "w-full",
|
||||||
|
false: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange"> &
|
||||||
|
VariantProps<typeof sliderTrackVariants> &
|
||||||
|
VariantProps<typeof sliderThumbVariants> &
|
||||||
|
VariantProps<typeof sliderContainerVariants> &
|
||||||
|
Props;
|
||||||
|
|
||||||
|
export const Slider = forwardRef<HTMLInputElement, SliderProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
containerClassName,
|
||||||
|
trackClassName,
|
||||||
|
fillClassName,
|
||||||
|
thumbClassName,
|
||||||
|
min = 0,
|
||||||
|
max = 100,
|
||||||
|
step = 1,
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
isDisabled = false,
|
||||||
|
isFullWidth = true,
|
||||||
|
isRequired = false,
|
||||||
|
showValue = false,
|
||||||
|
valuePosition = "top",
|
||||||
|
variant = "default",
|
||||||
|
size = "md",
|
||||||
|
onChange,
|
||||||
|
onChangeComplete,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
): JSX.Element => {
|
||||||
|
let initialValue = min;
|
||||||
|
if (value !== undefined) {
|
||||||
|
initialValue = Number(value);
|
||||||
|
} else if (defaultValue !== undefined) {
|
||||||
|
initialValue = Number(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [currentValue, setCurrentValue] = useState<number>(initialValue);
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const percentage = Math.max(0, Math.min(100, ((currentValue - min) / (max - min)) * 100));
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => inputRef.current as HTMLInputElement, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== undefined && Number(value) !== currentValue) {
|
||||||
|
setCurrentValue(Number(value));
|
||||||
|
}
|
||||||
|
}, [value, currentValue]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newValue = Number(e.target.value);
|
||||||
|
setCurrentValue(newValue);
|
||||||
|
onChange?.(newValue);
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseDown = useCallback(() => {
|
||||||
|
if (!isDisabled) {
|
||||||
|
setIsDragging(true);
|
||||||
|
}
|
||||||
|
}, [isDisabled]);
|
||||||
|
|
||||||
|
const handleChangeComplete = useCallback(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
onChangeComplete?.(currentValue);
|
||||||
|
setIsDragging(false);
|
||||||
|
}
|
||||||
|
}, [isDragging, currentValue, onChangeComplete]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
const handleGlobalMouseUp = () => handleChangeComplete();
|
||||||
|
|
||||||
|
document.addEventListener("mouseup", handleGlobalMouseUp);
|
||||||
|
document.addEventListener("touchend", handleGlobalMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mouseup", handleGlobalMouseUp);
|
||||||
|
document.removeEventListener("touchend", handleGlobalMouseUp);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
}, [isDragging, handleChangeComplete]);
|
||||||
|
|
||||||
|
const ValueDisplay = showValue ? (
|
||||||
|
<div className="text-xs text-bunker-300">{currentValue}</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
sliderContainerVariants({ isFullWidth, className: containerClassName }),
|
||||||
|
"my-2"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{showValue && valuePosition === "top" && ValueDisplay}
|
||||||
|
|
||||||
|
<div className="relative flex w-full items-center">
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
sliderTrackVariants({ variant, isDisabled, className: trackClassName })
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
sliderFillVariants({ variant, isDisabled, className: fillClassName }),
|
||||||
|
"left-0"
|
||||||
|
)}
|
||||||
|
style={{ width: `${percentage}%` }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
sliderThumbVariants({ variant, isDisabled, size, className: thumbClassName })
|
||||||
|
)}
|
||||||
|
style={{ left: `${percentage}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
value={currentValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onTouchStart={handleMouseDown}
|
||||||
|
disabled={isDisabled}
|
||||||
|
required={isRequired}
|
||||||
|
ref={inputRef}
|
||||||
|
className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showValue && valuePosition === "right" && (
|
||||||
|
<div className="ml-2 text-xs text-bunker-300">{currentValue}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Slider.displayName = "Slider";
|
||||||
2
frontend/src/components/v2/Slider/index.tsx
Normal file
2
frontend/src/components/v2/Slider/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export type { SliderProps } from "./Slider";
|
||||||
|
export { Slider } from "./Slider";
|
||||||
@@ -24,10 +24,12 @@ export * from "./Modal";
|
|||||||
export * from "./NoticeBanner";
|
export * from "./NoticeBanner";
|
||||||
export * from "./PageHeader";
|
export * from "./PageHeader";
|
||||||
export * from "./Pagination";
|
export * from "./Pagination";
|
||||||
|
export * from "./PasswordGenerator";
|
||||||
export * from "./Popoverv2";
|
export * from "./Popoverv2";
|
||||||
export * from "./SecretInput";
|
export * from "./SecretInput";
|
||||||
export * from "./Select";
|
export * from "./Select";
|
||||||
export * from "./Skeleton";
|
export * from "./Skeleton";
|
||||||
|
export * from "./Slider";
|
||||||
export * from "./Spinner";
|
export * from "./Spinner";
|
||||||
export * from "./Stepper";
|
export * from "./Stepper";
|
||||||
export * from "./Switch";
|
export * from "./Switch";
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
FilterableSelect,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
PasswordGenerator
|
||||||
|
} from "@app/components/v2";
|
||||||
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import {
|
import {
|
||||||
@@ -226,10 +232,13 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
|||||||
isError={Boolean(errors?.value)}
|
isError={Boolean(errors?.value)}
|
||||||
errorText={errors?.value?.message}
|
errorText={errors?.value?.message}
|
||||||
>
|
>
|
||||||
<InfisicalSecretInput
|
<div className="flex items-center gap-2">
|
||||||
{...field}
|
<InfisicalSecretInput
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
{...field}
|
||||||
/>
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||||
|
/>
|
||||||
|
<PasswordGenerator onUsePassword={field.onChange} />
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
import { Button, FormControl, Input, PasswordGenerator } from "@app/components/v2";
|
||||||
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||||
@@ -162,12 +162,15 @@ export const CreateSecretForm = ({
|
|||||||
isError={Boolean(errors?.value)}
|
isError={Boolean(errors?.value)}
|
||||||
errorText={errors?.value?.message}
|
errorText={errors?.value?.message}
|
||||||
>
|
>
|
||||||
<InfisicalSecretInput
|
<div className="flex items-center gap-2">
|
||||||
{...field}
|
<InfisicalSecretInput
|
||||||
environment={environment}
|
{...field}
|
||||||
secretPath={secretPath}
|
environment={environment}
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
secretPath={secretPath}
|
||||||
/>
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||||
|
/>
|
||||||
|
<PasswordGenerator onUsePassword={field.onChange} />
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user