mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 07:28:51 +00:00
Merge pull request #1403 from Infisical/daniel/improve-reminders
(Fix): Improve reminders
This commit is contained in:
+38
-19
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faClock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faClock, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -9,20 +9,28 @@ import { z } from "zod";
|
||||
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
|
||||
|
||||
const ReminderFormSchema = z.object({
|
||||
note: z.string().optional(),
|
||||
note: z.string().optional().nullable(),
|
||||
days: z
|
||||
.number()
|
||||
.min(1, { message: "Must be at least 1 day" })
|
||||
.max(365, { message: "Must be less than 365 days" })
|
||||
.nullable()
|
||||
});
|
||||
export type TReminderFormSchema = z.infer<typeof ReminderFormSchema>;
|
||||
|
||||
interface ReminderFormProps {
|
||||
isOpen: boolean;
|
||||
repeatDays?: number | null;
|
||||
note?: string | null;
|
||||
onOpenChange: (isOpen: boolean, data?: TReminderFormSchema) => void;
|
||||
}
|
||||
|
||||
export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps) => {
|
||||
export const CreateReminderForm = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
repeatDays,
|
||||
note
|
||||
}: ReminderFormProps) => {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
@@ -31,32 +39,31 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
handleSubmit,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TReminderFormSchema>({
|
||||
defaultValues: {
|
||||
days: repeatDays || undefined,
|
||||
note: note || ""
|
||||
},
|
||||
resolver: zodResolver(ReminderFormSchema)
|
||||
});
|
||||
|
||||
const handleFormSubmit = async (data: TReminderFormSchema) => {
|
||||
console.log(data);
|
||||
onOpenChange(false, data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
reset();
|
||||
reset({
|
||||
days: repeatDays || undefined,
|
||||
note: note || ""
|
||||
});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title="Create secret reminder"
|
||||
// ? QUESTION: Should this specifically say its for secret rotation?
|
||||
// ? Or should we be call it something more generic?
|
||||
subTitle={
|
||||
<div>
|
||||
Set up a reminder for when this secret should be rotated. Everyone with access to this
|
||||
project will be notified when the reminder is triggered.
|
||||
</div>
|
||||
}
|
||||
title={`${repeatDays ? "Update" : "Create"} reminder`}
|
||||
subTitle="Set up a reminder for when this secret should be rotated. Everyone with access to this project will be notified when the reminder is triggered."
|
||||
>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="space-y-2">
|
||||
@@ -68,7 +75,7 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
<>
|
||||
<FormControl
|
||||
className="mb-0"
|
||||
label="How many days between"
|
||||
label="Reminder Interval (in days)"
|
||||
isError={Boolean(fieldState.error)}
|
||||
errorText={fieldState.error?.message || ""}
|
||||
>
|
||||
@@ -76,6 +83,7 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
onChange={(el) => setValue("days", parseInt(el.target.value, 10))}
|
||||
type="number"
|
||||
placeholder="31"
|
||||
value={field.value || undefined}
|
||||
/>
|
||||
</FormControl>
|
||||
<div
|
||||
@@ -84,7 +92,8 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
field.value ? "opacity-60" : "opacity-0"
|
||||
)}
|
||||
>
|
||||
Every {field.value > 1 ? `${field.value} days` : "day"}
|
||||
A reminder will be sent every{" "}
|
||||
{field.value && field.value > 1 ? `${field.value} days` : "day"}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -102,17 +111,27 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className="mt-7 flex items-center">
|
||||
<div className="mt-7 flex items-center space-x-4">
|
||||
<Button
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
key="layout-create-project-submit"
|
||||
className="mr-4"
|
||||
className=""
|
||||
leftIcon={<FontAwesomeIcon icon={faClock} />}
|
||||
type="submit"
|
||||
>
|
||||
Create reminder
|
||||
{repeatDays ? "Update" : "Create"} reminder
|
||||
</Button>
|
||||
{repeatDays && (
|
||||
<Button
|
||||
key="layout-cancel-create-project"
|
||||
onClick={() => onOpenChange(false, { days: null, note: null })}
|
||||
colorSchema="danger"
|
||||
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
||||
>
|
||||
Delete reminder
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
key="layout-cancel-create-project"
|
||||
onClick={() => onOpenChange(false)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
faCheckCircle,
|
||||
faCircle,
|
||||
faCircleDot,
|
||||
faClock,
|
||||
faPlus,
|
||||
faTag
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -33,9 +34,11 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useGetSecretVersion } from "@app/hooks/api";
|
||||
import { DecryptedSecret, UserWsKeyPair, WsTag } from "@app/hooks/api/types";
|
||||
|
||||
import { CreateReminderForm } from "./CreateReminderForm";
|
||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||
|
||||
type Props = {
|
||||
@@ -147,13 +150,34 @@ export const SecretDetailSidebar = ({
|
||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||
};
|
||||
|
||||
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
||||
|
||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||
const secretReminderNote = watch("reminderNote");
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateReminderForm
|
||||
repeatDays={secretReminderRepeatDays}
|
||||
note={secretReminderNote}
|
||||
isOpen={createReminderFormOpen}
|
||||
onOpenChange={(_, data) => {
|
||||
setCreateReminderFormOpen.toggle();
|
||||
|
||||
if (data) {
|
||||
setValue("reminderRepeatDays", data.days, { shouldDirty: true });
|
||||
setValue("reminderNote", data.note, { shouldDirty: true });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Drawer
|
||||
onOpenChange={(state) => {
|
||||
if (isOpen && isDirty) {
|
||||
if (
|
||||
// eslint-disable-next-line no-alert
|
||||
window.confirm("You have edited the secret. Are you sure you want to reset the change?")
|
||||
window.confirm(
|
||||
"You have edited the secret. Are you sure you want to reset the change?"
|
||||
)
|
||||
) {
|
||||
onToggle(false);
|
||||
reset();
|
||||
@@ -313,6 +337,39 @@ export const SecretDetailSidebar = ({
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormControl label="Reminder">
|
||||
{secretReminderRepeatDays && secretReminderRepeatDays > 0 ? (
|
||||
<div className="mt-2 ml-1 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FontAwesomeIcon className="text-primary-500" icon={faClock} />
|
||||
<span className="text-sm text-bunker-300">
|
||||
Reminder every {secretReminderRepeatDays}{" "}
|
||||
{secretReminderRepeatDays > 1 ? "days" : "day"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className="px-2 py-1"
|
||||
variant="outline_bg"
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2 ml-1 flex items-center space-x-2">
|
||||
<Button
|
||||
className="px-2 py-1"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faClock} />}
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
>
|
||||
Create Reminder
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl label="Comments & Notes">
|
||||
<TextArea
|
||||
className="border border-mineshaft-600 text-sm"
|
||||
@@ -404,5 +461,6 @@ export const SecretDetailSidebar = ({
|
||||
</form>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -111,9 +111,11 @@ export const SecretItem = memo(
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||
const secretReminderNote = watch("reminderNote");
|
||||
|
||||
const overrideAction = watch("overrideAction");
|
||||
const hasComment = Boolean(watch("comment"));
|
||||
const hasReminder = Boolean(watch("reminderRepeatDays"));
|
||||
|
||||
const selectedTags = watch("tags", []);
|
||||
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
|
||||
@@ -191,6 +193,8 @@ export const SecretItem = memo(
|
||||
return (
|
||||
<>
|
||||
<CreateReminderForm
|
||||
repeatDays={secretReminderRepeatDays}
|
||||
note={secretReminderNote}
|
||||
isOpen={createReminderFormOpen}
|
||||
onOpenChange={(_, data) => {
|
||||
setCreateReminderFormOpen.toggle();
|
||||
@@ -380,22 +384,24 @@ export const SecretItem = memo(
|
||||
<IconButton
|
||||
className={twMerge(
|
||||
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6",
|
||||
hasReminder && "w-5 text-primary"
|
||||
Boolean(secretReminderRepeatDays) && "w-5 text-primary"
|
||||
)}
|
||||
variant="plain"
|
||||
size="md"
|
||||
ariaLabel="add-reminder"
|
||||
>
|
||||
<Tooltip content="Reminder">
|
||||
<FontAwesomeIcon
|
||||
onClick={() => {
|
||||
if (!hasReminder) {
|
||||
setCreateReminderFormOpen.on();
|
||||
} else {
|
||||
setValue("reminderRepeatDays", null, { shouldDirty: true });
|
||||
setValue("reminderNote", null, { shouldDirty: true });
|
||||
<Tooltip
|
||||
content={
|
||||
secretReminderRepeatDays && secretReminderRepeatDays > 0
|
||||
? `Every ${secretReminderRepeatDays} day${
|
||||
Number(secretReminderRepeatDays) > 1 ? "s" : ""
|
||||
}
|
||||
}}
|
||||
`
|
||||
: "Reminder"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
icon={faClock}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
Reference in New Issue
Block a user