[WEB-1127] style: create and delete modals' consistency (#4345)

* style: update modals typography, alignment

* style: made the modal separator full width

* style: delete modals consistency

* style: update the remaining delete modals

* chore: delete modal secondary button text

* style: update the remaining create modals

* chore: update cancel button text

* chore: created modal core

* style: modals responsiveness
This commit is contained in:
Aaryan Khandelwal 2024-05-07 12:44:36 +05:30 committed by GitHub
parent 5ef51edad7
commit 20e7dc68e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 1375 additions and 2161 deletions

View File

@ -1,15 +1,16 @@
import { useState, Fragment, FC } from "react"; import { useState, FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
import { Dialog, Transition } from "@headlessui/react";
import { IApiToken } from "@plane/types";
// services
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { API_TOKENS_LIST } from "@/constants/fetch-keys";
import { APITokenService } from "@/services/api_token.service";
// ui
// types // types
import { IApiToken } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// fetch-keys // fetch-keys
import { API_TOKENS_LIST } from "@/constants/fetch-keys";
// services
import { APITokenService } from "@/services/api_token.service";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -32,12 +33,12 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
setDeleteLoading(false); setDeleteLoading(false);
}; };
const handleDeletion = () => { const handleDeletion = async () => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
setDeleteLoading(true); setDeleteLoading(true);
apiTokenService await apiTokenService
.deleteApiToken(workspaceSlug.toString(), tokenId) .deleteApiToken(workspaceSlug.toString(), tokenId)
.then(() => { .then(() => {
setToast({ setToast({
@ -65,58 +66,17 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDeletion}
as={Fragment} isDeleting={deleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete API token"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100" Any application using this token will no longer have the access to Plane data. This action cannot be undone.
leaveTo="opacity-0" </>
> }
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" /> />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-3 p-4">
<div className="flex w-full items-center justify-start">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">
Are you sure you want to delete the token?
</h3>
</div>
<span>
<p className="text-sm text-custom-text-400">
Any application using this token will no longer have the access to Plane data. This action cannot
be undone.
</p>
</span>
<div className="mt-2 flex justify-end gap-2">
<Button variant="neutral-primary" onClick={handleClose} size="sm">
Cancel
</Button>
<Button variant="danger" onClick={handleDeletion} loading={deleteLoading} size="sm">
{deleteLoading ? "Deleting..." : "Delete"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -1,21 +1,20 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
import { Dialog, Transition } from "@headlessui/react"; // types
import { IApiToken } from "@plane/types"; import { IApiToken } from "@plane/types";
// services // ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { CreateApiTokenForm, GeneratedTokenDetails } from "@/components/api-token"; import { CreateApiTokenForm, GeneratedTokenDetails } from "@/components/api-token";
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// fetch-keys
import { API_TOKENS_LIST } from "@/constants/fetch-keys"; import { API_TOKENS_LIST } from "@/constants/fetch-keys";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper"; import { renderFormattedDate } from "@/helpers/date-time.helper";
import { csvDownload } from "@/helpers/download.helper"; import { csvDownload } from "@/helpers/download.helper";
// services
import { APITokenService } from "@/services/api_token.service"; import { APITokenService } from "@/services/api_token.service";
// ui
// components
// helpers
// types
// fetch-keys
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -86,32 +85,7 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <ModalCore isOpen={isOpen} handleClose={() => {}} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<Dialog as="div" className="relative z-20" onClose={() => {}}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="grid h-full w-full place-items-center p-4 text-center">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 px-4 text-left shadow-custom-shadow-md transition-all w-full sm:max-w-2xl">
{generatedToken ? ( {generatedToken ? (
<GeneratedTokenDetails handleClose={handleClose} tokenDetails={generatedToken} /> <GeneratedTokenDetails handleClose={handleClose} tokenDetails={generatedToken} />
) : ( ) : (
@ -122,11 +96,6 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
onSubmit={handleCreateToken} onSubmit={handleCreateToken}
/> />
)} )}
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -2,13 +2,15 @@ import { useState } from "react";
import { add } from "date-fns"; import { add } from "date-fns";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { Calendar } from "lucide-react"; import { Calendar } from "lucide-react";
// types
import { IApiToken } from "@plane/types"; import { IApiToken } from "@plane/types";
// ui // ui
import { Button, CustomSelect, Input, TextArea, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, CustomSelect, Input, TextArea, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { DateDropdown } from "@/components/dropdowns"; import { DateDropdown } from "@/components/dropdowns";
// helpers // helpers
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { renderFormattedDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// types
type Props = { type Props = {
handleClose: () => void; handleClose: () => void;
@ -106,13 +108,14 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
const today = new Date(); const today = new Date();
const tomorrow = add(today, { days: 1 }); const tomorrow = add(today, { days: 1 });
const expiredAt = watch("expired_at");
return ( return (
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="space-y-4"> <div className="space-y-5 p-5">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">Create token</h3> <h3 className="text-xl font-medium text-custom-text-200">Create token</h3>
<div className="space-y-3"> <div className="space-y-3">
<div> <div className="space-y-1">
<Controller <Controller
control={control} control={control}
name="label" name="label"
@ -130,8 +133,8 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
value={value} value={value}
onChange={onChange} onChange={onChange}
hasError={Boolean(errors.label)} hasError={Boolean(errors.label)}
placeholder="Token title" placeholder="Title"
className="w-full text-sm font-medium" className="w-full text-base"
/> />
)} )}
/> />
@ -145,8 +148,8 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
value={value} value={value}
onChange={onChange} onChange={onChange}
hasError={Boolean(errors.description)} hasError={Boolean(errors.description)}
placeholder="Token description" placeholder="Description"
className="min-h-24 w-full text-sm" className="w-full text-base resize-none min-h-24"
/> />
)} )}
/> />
@ -162,9 +165,12 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
<CustomSelect <CustomSelect
customButton={ customButton={
<div <div
className={`flex items-center gap-2 rounded border-[0.5px] border-custom-border-300 px-2 py-0.5 ${ className={cn(
neverExpires ? "text-custom-text-400" : "" "h-7 flex items-center gap-2 rounded border-[0.5px] border-custom-border-300 px-2 py-0.5",
}`} {
"text-custom-text-400": neverExpires,
}
)}
> >
<Calendar className="h-3 w-3" /> <Calendar className="h-3 w-3" />
{value === "custom" {value === "custom"
@ -188,7 +194,8 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
); );
}} }}
/> />
{watch("expired_at") === "custom" && ( {expiredAt === "custom" && (
<div className="h-7">
<DateDropdown <DateDropdown
value={customDate} value={customDate}
onChange={(date) => setCustomDate(date)} onChange={(date) => setCustomDate(date)}
@ -198,23 +205,24 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
placeholder="Set date" placeholder="Set date"
disabled={neverExpires} disabled={neverExpires}
/> />
</div>
)} )}
</div> </div>
{!neverExpires && ( {!neverExpires && (
<span className="text-xs text-custom-text-400"> <span className="text-xs text-custom-text-400">
{watch("expired_at") === "custom" {expiredAt === "custom"
? customDate ? customDate
? `Expires ${renderFormattedDate(customDate)}` ? `Expires ${renderFormattedDate(customDate)}`
: null : null
: watch("expired_at") : expiredAt
? `Expires ${getExpiryDate(watch("expired_at") ?? "")}` ? `Expires ${getExpiryDate(expiredAt ?? "")}`
: null} : null}
</span> </span>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div className="mt-5 flex items-center justify-between gap-2"> <div className="px-5 py-4 flex items-center justify-between gap-2 border-t-[0.5px] border-custom-border-200">
<div className="flex cursor-pointer items-center gap-1.5" onClick={toggleNeverExpires}> <div className="flex cursor-pointer items-center gap-1.5" onClick={toggleNeverExpires}>
<div className="flex cursor-pointer items-center justify-center"> <div className="flex cursor-pointer items-center justify-center">
<ToggleSwitch value={neverExpires} onChange={() => {}} size="sm" /> <ToggleSwitch value={neverExpires} onChange={() => {}} size="sm" />
@ -223,10 +231,10 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={handleClose}> <Button variant="neutral-primary" size="sm" onClick={handleClose}>
Discard Cancel
</Button> </Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}> <Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{isSubmitting ? "Generating..." : "Generate token"} {isSubmitting ? "Generating" : "Generate token"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -28,7 +28,7 @@ export const GeneratedTokenDetails: React.FC<Props> = (props) => {
}; };
return ( return (
<div className="w-full"> <div className="w-full p-5">
<div className="w-full space-y-3 text-wrap"> <div className="w-full space-y-3 text-wrap">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">Key created</h3> <h3 className="text-lg font-medium leading-6 text-custom-text-100">Key created</h3>
<p className="text-sm text-custom-text-400"> <p className="text-sm text-custom-text-400">

View File

@ -0,0 +1,90 @@
import { AlertTriangle, LucideIcon } from "lucide-react";
// ui
import { Button, TButtonVariant } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// helpers
import { cn } from "@/helpers/common.helper";
export type TModalVariant = "danger";
type Props = {
content: React.ReactNode | string;
handleClose: () => void;
handleSubmit: () => Promise<void>;
hideIcon?: boolean;
isDeleting: boolean;
isOpen: boolean;
position?: EModalPosition;
primaryButtonText?: {
loading: string;
default: string;
};
secondaryButtonText?: string;
title: string;
variant?: TModalVariant;
width?: EModalWidth;
};
const VARIANT_ICONS: Record<TModalVariant, LucideIcon> = {
danger: AlertTriangle,
};
const BUTTON_VARIANTS: Record<TModalVariant, TButtonVariant> = {
danger: "danger",
};
const VARIANT_CLASSES: Record<TModalVariant, string> = {
danger: "bg-red-500/20 text-red-500",
};
export const AlertModalCore: React.FC<Props> = (props) => {
const {
content,
handleClose,
handleSubmit,
hideIcon = false,
isDeleting,
isOpen,
position = EModalPosition.CENTER,
primaryButtonText = {
loading: "Deleting",
default: "Delete",
},
secondaryButtonText = "Cancel",
title,
variant = "danger",
width = EModalWidth.XL,
} = props;
const Icon = VARIANT_ICONS[variant];
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={position} width={width}>
<div className="p-5 flex flex-col sm:flex-row items-center sm:items-start gap-4">
{!hideIcon && (
<span
className={cn(
"flex-shrink-0 grid place-items-center rounded-full size-12 sm:size-10",
VARIANT_CLASSES[variant]
)}
>
<Icon className="size-5" aria-hidden="true" />
</span>
)}
<div className="text-center sm:text-left">
<h3 className="text-lg font-medium">{title}</h3>
<p className="mt-1 text-sm text-custom-text-200">{content}</p>
</div>
</div>
<div className="px-5 py-4 flex flex-col-reverse sm:flex-row sm:justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
{secondaryButtonText}
</Button>
<Button variant={BUTTON_VARIANTS[variant]} size="sm" tabIndex={1} onClick={handleSubmit} loading={isDeleting}>
{isDeleting ? primaryButtonText.loading : primaryButtonText.default}
</Button>
</div>
</ModalCore>
);
};

View File

@ -1,7 +1,9 @@
export * from "./alert-modal";
export * from "./bulk-delete-issues-modal"; export * from "./bulk-delete-issues-modal";
export * from "./existing-issues-list-modal"; export * from "./existing-issues-list-modal";
export * from "./gpt-assistant-popover"; export * from "./gpt-assistant-popover";
export * from "./link-modal"; export * from "./link-modal";
export * from "./modal-core";
export * from "./user-image-upload-modal"; export * from "./user-image-upload-modal";
export * from "./workspace-image-upload-modal"; export * from "./workspace-image-upload-modal";
export * from "./issue-search-modal-empty-state"; export * from "./issue-search-modal-empty-state";

View File

@ -0,0 +1,68 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
// helpers
import { cn } from "@/helpers/common.helper";
export enum EModalPosition {
TOP = "flex items-center justify-center text-center mx-4 my-10 md:my-20",
CENTER = "flex items-end sm:items-center justify-center p-4 min-h-full",
}
export enum EModalWidth {
XL = "sm:max-w-xl",
XXL = "sm:max-w-2xl",
XXXL = "sm:max-w-3xl",
XXXXL = "sm:max-w-4xl",
}
type Props = {
children: React.ReactNode;
handleClose: () => void;
isOpen: boolean;
position?: EModalPosition;
width?: EModalWidth;
};
export const ModalCore: React.FC<Props> = (props) => {
const { children, handleClose, isOpen, position = EModalPosition.CENTER, width = EModalWidth.XXL } = props;
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className={position}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel
className={cn(
"relative transform rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all w-full",
width
)}
>
{children}
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};

View File

@ -1,16 +1,16 @@
import { Fragment, useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
import { ICycle } from "@plane/types";
// hooks
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { CYCLE_DELETED } from "@/constants/event-tracker";
import { useEventTracker, useCycle } from "@/hooks/store";
// components
// types // types
import { ICycle } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// constants // constants
import { CYCLE_DELETED } from "@/constants/event-tracker";
// hooks
import { useEventTracker, useCycle } from "@/hooks/store";
interface ICycleDelete { interface ICycleDelete {
cycle: ICycle; cycle: ICycle;
@ -24,12 +24,12 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
const { isOpen, handleClose, cycle, workspaceSlug, projectId } = props; const { isOpen, handleClose, cycle, workspaceSlug, projectId } = props;
// states // states
const [loader, setLoader] = useState(false); const [loader, setLoader] = useState(false);
// router
const router = useRouter();
const { cycleId, peekCycle } = router.query;
// store hooks // store hooks
const { captureCycleEvent } = useEventTracker(); const { captureCycleEvent } = useEventTracker();
const { deleteCycle } = useCycle(); const { deleteCycle } = useCycle();
// router
const router = useRouter();
const { cycleId, peekCycle } = router.query;
const formSubmit = async () => { const formSubmit = async () => {
if (!cycle) return; if (!cycle) return;
@ -70,66 +70,19 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
}; };
return ( return (
<div> <AlertModalCore
<div> handleClose={handleClose}
<Transition.Root show={isOpen} as={Fragment}> handleSubmit={formSubmit}
<Dialog as="div" className="relative z-20" onClose={handleClose}> isDeleting={loader}
<Transition.Child isOpen={isOpen}
as={Fragment} title="Delete Cycle"
enter="ease-out duration-300" content={
enterFrom="opacity-0" <>
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-4">
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20">
<AlertTriangle width={16} strokeWidth={2} className="text-red-600" />
</div>
<div className="text-xl font-medium 2xl:text-2xl">Delete cycle</div>
</div>
<span>
<p className="text-sm text-custom-text-200">
Are you sure you want to delete cycle{' "'} Are you sure you want to delete cycle{' "'}
<span className="break-words font-medium text-custom-text-100">{cycle?.name}</span> <span className="break-words font-medium text-custom-text-100">{cycle?.name}</span>
{'"'}? All of the data related to the cycle will be permanently removed. This action cannot be {'"'}? All of the data related to the cycle will be permanently removed. This action cannot be undone.
undone. </>
</p> }
</span> />
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={formSubmit} loading={loader}>
{loader ? "Deleting" : "Delete"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</div>
</div>
); );
}); });

View File

@ -1,12 +1,12 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// types
import { ICycle } from "@plane/types"; import { ICycle } from "@plane/types";
// ui
import { Button, Input, TextArea } from "@plane/ui"; import { Button, Input, TextArea } from "@plane/ui";
// components
import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns"; import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns";
// helpers
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
import { shouldRenderProject } from "@/helpers/project.helper"; import { shouldRenderProject } from "@/helpers/project.helper";
@ -53,36 +53,37 @@ export const CycleForm: React.FC<Props> = (props) => {
return ( return (
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}> <form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
<div className="space-y-5"> <div className="space-y-5 p-5">
<div className="flex items-center gap-x-3"> <div className="flex items-center gap-x-3">
{!status && ( {!status && (
<Controller <Controller
control={control} control={control}
name="project_id" name="project_id"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<div className="h-7">
<ProjectDropdown <ProjectDropdown
value={value} value={value}
onChange={(val) => { onChange={(val) => {
onChange(val); onChange(val);
setActiveProject(val); setActiveProject(val);
}} }}
buttonVariant="background-with-text" buttonVariant="border-with-text"
renderCondition={(project) => shouldRenderProject(project)} renderCondition={(project) => shouldRenderProject(project)}
tabIndex={7} tabIndex={7}
/> />
</div>
)} )}
/> />
)} )}
<h3 className="text-xl font-medium leading-6 text-custom-text-200">{status ? "Update" : "New"} Cycle</h3> <h3 className="text-xl font-medium text-custom-text-200">{status ? "Update" : "Create"} Cycle</h3>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<div className="mt-2 space-y-3"> <div className="space-y-1">
<div className="flex flex-col gap-1">
<Controller <Controller
name="name" name="name"
control={control} control={control}
rules={{ rules={{
required: "Name is required", required: "Title is required",
maxLength: { maxLength: {
value: 255, value: 255,
message: "Title should be less than 255 characters", message: "Title should be less than 255 characters",
@ -90,16 +91,16 @@ export const CycleForm: React.FC<Props> = (props) => {
}} }}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<Input <Input
id="cycle_name"
name="name" name="name"
type="text" type="text"
placeholder="Cycle Title" placeholder="Title"
className="w-full resize-none placeholder:text-sm placeholder:font-medium focus:border-blue-400" className="w-full text-base"
value={value} value={value}
inputSize="md" inputSize="md"
onChange={onChange} onChange={onChange}
hasError={Boolean(errors?.name)} hasError={Boolean(errors?.name)}
tabIndex={1} tabIndex={1}
autoFocus
/> />
)} )}
/> />
@ -111,10 +112,9 @@ export const CycleForm: React.FC<Props> = (props) => {
control={control} control={control}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<TextArea <TextArea
id="cycle_description"
name="description" name="description"
placeholder="Description..." placeholder="Description"
className="w-full text-sm resize-none min-h-24" className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
value={value} value={value}
onChange={onChange} onChange={onChange}
@ -123,7 +123,6 @@ export const CycleForm: React.FC<Props> = (props) => {
)} )}
/> />
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Controller <Controller
control={control} control={control}
@ -161,13 +160,12 @@ export const CycleForm: React.FC<Props> = (props) => {
</div> </div>
</div> </div>
</div> </div>
</div> <div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-100 pt-5 ">
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}> <Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}>
Cancel Cancel
</Button> </Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={5}> <Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={5}>
{data ? (isSubmitting ? "Updating" : "Update cycle") : isSubmitting ? "Creating" : "Create cycle"} {data ? (isSubmitting ? "Updating" : "Update Cycle") : isSubmitting ? "Creating" : "Create Cycle"}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -1,18 +1,18 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react"; // types
import type { CycleDateCheckData, ICycle, TCycleTabOptions } from "@plane/types"; import type { CycleDateCheckData, ICycle, TCycleTabOptions } from "@plane/types";
// services // ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { CycleForm } from "@/components/cycles"; import { CycleForm } from "@/components/cycles";
// constants
import { CYCLE_CREATED, CYCLE_UPDATED } from "@/constants/event-tracker"; import { CYCLE_CREATED, CYCLE_UPDATED } from "@/constants/event-tracker";
// hooks
import { useEventTracker, useCycle, useProject } from "@/hooks/store"; import { useEventTracker, useCycle, useProject } from "@/hooks/store";
import useLocalStorage from "@/hooks/use-local-storage"; import useLocalStorage from "@/hooks/use-local-storage";
// services
import { CycleService } from "@/services/cycle.service"; import { CycleService } from "@/services/cycle.service";
// hooks
// components
// ui
// types
// constants
type CycleModalProps = { type CycleModalProps = {
isOpen: boolean; isOpen: boolean;
@ -166,32 +166,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
}, [activeProject, data, projectId, workspaceProjectIds, isOpen]); }, [activeProject, data, projectId, workspaceProjectIds, isOpen]);
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<CycleForm <CycleForm
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
handleClose={handleClose} handleClose={handleClose}
@ -200,11 +175,6 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
setActiveProject={setActiveProject} setActiveProject={setActiveProject}
data={data} data={data}
/> />
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -2,15 +2,16 @@ import React, { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react";
import { IEstimate, IEstimateFormData } from "@plane/types";
// store hooks
import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
import { checkDuplicates } from "@/helpers/array.helper";
import { useEstimate } from "@/hooks/store";
// ui
// helpers
// types // types
import { IEstimate, IEstimateFormData } from "@plane/types";
// ui
import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// helpers
import { checkDuplicates } from "@/helpers/array.helper";
// hooks
import { useEstimate } from "@/hooks/store";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -196,36 +197,11 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
}, [data, reset]); }, [data, reset]);
return ( return (
<> <ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-5 p-5">
<div className="text-xl font-medium text-custom-text-200">{data ? "Update" : "Create"} Estimate</div>
<div className="space-y-3"> <div className="space-y-3">
<div className="text-lg font-medium leading-6">{data ? "Update" : "Create"} Estimate</div>
<div> <div>
<Controller <Controller
control={control} control={control}
@ -240,7 +216,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
ref={ref} ref={ref}
hasError={Boolean(errors.name)} hasError={Boolean(errors.name)}
placeholder="Title" placeholder="Title"
className="w-full resize-none text-xl" className="w-full text-base"
/> />
)} )}
/> />
@ -256,13 +232,13 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
value={value} value={value}
placeholder="Description" placeholder="Description"
onChange={onChange} onChange={onChange}
className="mt-3 min-h-32 resize-none text-sm" className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
/> />
)} )}
/> />
</div> </div>
</div>
{/* list of all the points */} {/* list of all the points */}
{/* since they are all the same, we can use a loop to render them */} {/* since they are all the same, we can use a loop to render them */}
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-3 gap-3">
@ -302,27 +278,15 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
))} ))}
</div> </div>
</div> </div>
<div className="mt-5 flex justify-end gap-2"> <div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose}> <Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel Cancel
</Button> </Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}> <Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{data {data ? (isSubmitting ? "Updating" : "Update Estimate") : isSubmitting ? "Creating" : "Create Estimate"}
? isSubmitting
? "Updating Estimate..."
: "Update Estimate"
: isSubmitting
? "Creating Estimate..."
: "Create Estimate"}
</Button> </Button>
</div> </div>
</form> </form>
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</>
); );
}); });

View File

@ -1,14 +1,14 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
import { IEstimate } from "@plane/types";
// store hooks
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { useEstimate } from "@/hooks/store";
// types // types
import { IEstimate } from "@plane/types";
// ui // ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// hooks
import { useEstimate } from "@/hooks/store";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -26,15 +26,16 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
// store hooks // store hooks
const { deleteEstimate } = useEstimate(); const { deleteEstimate } = useEstimate();
const handleEstimateDelete = () => { const handleEstimateDelete = async () => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
setIsDeleteLoading(true);
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const estimateId = data?.id!; const estimateId = data?.id!;
deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId) await deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId)
.then(() => { .then(() => {
setIsDeleteLoading(false);
handleClose(); handleClose();
}) })
.catch((err) => { .catch((err) => {
@ -46,7 +47,8 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
title: "Error!", title: "Error!",
message: errorString ?? "Estimate could not be deleted. Please try again", message: errorString ?? "Estimate could not be deleted. Please try again",
}); });
}); })
.finally(() => setIsDeleteLoading(false));
}; };
useEffect(() => { useEffect(() => {
@ -59,72 +61,19 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={onClose}> handleClose={onClose}
<Transition.Child handleSubmit={handleEstimateDelete}
as={React.Fragment} isDeleting={isDeleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete Estimate"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Estimate</h3>
</span>
</div>
<span>
<p className="break-words text-sm leading-7 text-custom-text-200">
Are you sure you want to delete estimate-{" "} Are you sure you want to delete estimate-{" "}
<span className="break-words font-medium text-custom-text-100">{data?.name}</span> <span className="break-words font-medium text-custom-text-100">{data?.name}</span>
{""}? All of the data related to the estiamte will be permanently removed. This action cannot be {""}? All of the data related to the estiamte will be permanently removed. This action cannot be undone.
undone. </>
</p> }
</span> />
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={() => {
setIsDeleteLoading(true);
handleEstimateDelete();
}}
loading={isDeleteLoading}
>
{isDeleteLoading ? "Deleting..." : "Delete Estimate"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,7 +1,9 @@
import { FC, FormEvent, useCallback, useRef, useState } from "react"; import { FC, FormEvent, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// editor
import { EditorRefApi } from "@plane/rich-text-editor"; import { EditorRefApi } from "@plane/rich-text-editor";
// types
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui";
// components // components
@ -120,7 +122,10 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
if (!workspaceSlug || !projectId || !workspaceId) return <></>; if (!workspaceSlug || !projectId || !workspaceId) return <></>;
return ( return (
<form className="relative space-y-4" onSubmit={handleFormSubmit}> <form onSubmit={handleFormSubmit}>
<div className="space-y-5 p-5">
<h3 className="text-xl font-medium text-custom-text-200">Create Inbox Issue</h3>
<div className="space-y-3">
<InboxIssueTitle <InboxIssueTitle
data={formData} data={formData}
handleData={handleFormData} handleData={handleFormData}
@ -136,15 +141,18 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
containerClassName="border-[0.5px] border-custom-border-200 py-3 min-h-[150px]" containerClassName="border-[0.5px] border-custom-border-200 py-3 min-h-[150px]"
/> />
<InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} /> <InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} />
<div className="relative flex justify-between items-center gap-3"> </div>
</div>
<div className="px-5 py-4 flex items-center justify-between gap-2 border-t-[0.5px] border-custom-border-200">
<div <div
className="flex cursor-pointer items-center gap-1.5" className="inline-flex items-center gap-1.5 cursor-pointer"
onClick={() => setCreateMore((prevData) => !prevData)} onClick={() => setCreateMore((prevData) => !prevData)}
role="button"
> >
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" /> <ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
<span className="text-xs">Create more</span> <span className="text-xs">Create more</span>
</div> </div>
<div className="relative flex items-center gap-3"> <div className="flex items-center gap-3">
<Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}> <Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}>
Discard Discard
</Button> </Button>
@ -155,7 +163,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
loading={formSubmitting} loading={formSubmitting}
disabled={isTitleLengthMoreThan255Character} disabled={isTitleLengthMoreThan255Character}
> >
{formSubmitting ? "Adding Issue..." : "Add Issue"} {formSubmitting ? "Creating" : "Create Issue"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,8 +1,11 @@
import { FC, useCallback, useEffect, useRef, useState } from "react"; import { FC, useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// editor
import { EditorRefApi } from "@plane/rich-text-editor"; import { EditorRefApi } from "@plane/rich-text-editor";
// types
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { import {
@ -15,7 +18,7 @@ import { ISSUE_UPDATED } from "@/constants/event-tracker";
// helpers // helpers
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// hooks // hooks
import { useEventTracker, useInboxIssues, useWorkspace } from "@/hooks/store"; import { useEventTracker, useInboxIssues, useProject, useWorkspace } from "@/hooks/store";
type TInboxIssueEditRoot = { type TInboxIssueEditRoot = {
workspaceSlug: string; workspaceSlug: string;
@ -31,8 +34,9 @@ export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
const router = useRouter(); const router = useRouter();
// refs // refs
const descriptionEditorRef = useRef<EditorRefApi>(null); const descriptionEditorRef = useRef<EditorRefApi>(null);
// hooks // store hooks
const { captureIssueEvent } = useEventTracker(); const { captureIssueEvent } = useEventTracker();
const { currentProjectDetails } = useProject();
const { updateProjectIssue } = useInboxIssues(issueId); const { updateProjectIssue } = useInboxIssues(issueId);
const { getWorkspaceBySlug } = useWorkspace(); const { getWorkspaceBySlug } = useWorkspace();
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id; const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id;
@ -125,7 +129,12 @@ export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
if (!workspaceSlug || !projectId || !workspaceId || !formData) return <></>; if (!workspaceSlug || !projectId || !workspaceId || !formData) return <></>;
return ( return (
<div className="relative space-y-4"> <>
<div className="space-y-5 p-5">
<h3 className="text-xl font-medium text-custom-text-200">
Move {currentProjectDetails?.identifier}-{issue?.sequence_id} to project issues
</h3>
<div className="space-y-3">
<InboxIssueTitle <InboxIssueTitle
data={formData} data={formData}
handleData={handleFormData} handleData={handleFormData}
@ -141,7 +150,9 @@ export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
containerClassName="border-[0.5px] border-custom-border-200 py-3 min-h-[150px]" containerClassName="border-[0.5px] border-custom-border-200 py-3 min-h-[150px]"
/> />
<InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} isVisible /> <InboxIssueProperties projectId={projectId} data={formData} handleData={handleFormData} isVisible />
<div className="relative flex justify-end items-center gap-3"> </div>
</div>
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}> <Button variant="neutral-primary" size="sm" type="button" onClick={handleModalClose}>
Cancel Cancel
</Button> </Button>
@ -153,9 +164,9 @@ export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
disabled={isTitleLengthMoreThan255Character} disabled={isTitleLengthMoreThan255Character}
onClick={handleFormSubmit} onClick={handleFormSubmit}
> >
{formSubmitting ? "Adding..." : "Add to project"} {formSubmitting ? "Adding" : "Add to project"}
</Button> </Button>
</div> </div>
</div> </>
); );
}); });

View File

@ -32,8 +32,8 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
<Loader.Item width="100%" height="140px" /> <Loader.Item width="100%" height="140px" />
</Loader> </Loader>
); );
return ( return (
<div className="relative">
<RichTextEditor <RichTextEditor
initialValue={!data?.description_html || data?.description_html === "" ? "<p></p>" : data?.description_html} initialValue={!data?.description_html || data?.description_html === "" ? "<p></p>" : data?.description_html}
ref={editorRef} ref={editorRef}
@ -45,6 +45,5 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
placeholder={getDescriptionPlaceholder} placeholder={getDescriptionPlaceholder}
containerClassName={containerClassName} containerClassName={containerClassName}
/> />
</div>
); );
}); });

View File

@ -13,7 +13,7 @@ export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
const { data, handleData, isTitleLengthMoreThan255Character } = props; const { data, handleData, isTitleLengthMoreThan255Character } = props;
return ( return (
<div className="relative flex flex-wrap gap-2 items-center"> <div className="space-y-1">
<Input <Input
id="name" id="name"
name="name" name="name"
@ -21,7 +21,7 @@ export const InboxIssueTitle: FC<TInboxIssueTitle> = observer((props) => {
value={data?.name} value={data?.name}
onChange={(e) => handleData("name", e.target.value)} onChange={(e) => handleData("name", e.target.value)}
placeholder="Title" placeholder="Title"
className="w-full resize-none text-xl" className="w-full text-base"
required required
/> />
{isTitleLengthMoreThan255Character && ( {isTitleLengthMoreThan255Character && (

View File

@ -1,11 +1,9 @@
import { FC, Fragment } from "react"; import { FC } from "react";
import { observer } from "mobx-react"; // types
import { Transition, Dialog } from "@headlessui/react";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { InboxIssueCreateRoot, InboxIssueEditRoot } from "@/components/inbox/modals/create-edit-modal"; import { InboxIssueCreateRoot, InboxIssueEditRoot } from "@/components/inbox/modals/create-edit-modal";
// hooks
import { useProject } from "@/hooks/store";
type TInboxIssueCreateEditModalRoot = { type TInboxIssueCreateEditModalRoot = {
workspaceSlug: string; workspaceSlug: string;
@ -16,44 +14,17 @@ type TInboxIssueCreateEditModalRoot = {
onSubmit?: () => void; onSubmit?: () => void;
}; };
export const InboxIssueCreateEditModalRoot: FC<TInboxIssueCreateEditModalRoot> = observer((props) => { export const InboxIssueCreateEditModalRoot: FC<TInboxIssueCreateEditModalRoot> = (props) => {
const { workspaceSlug, projectId, modalState, handleModalClose, issue, onSubmit } = props; const { workspaceSlug, projectId, modalState, handleModalClose, issue, onSubmit } = props;
// hooks
const { currentProjectDetails } = useProject();
return ( return (
<div> <ModalCore
<Transition.Root show={modalState} as={Fragment}> isOpen={modalState}
<Dialog as="div" className="relative z-20" onClose={handleModalClose}> handleClose={handleModalClose}
<Transition.Child position={EModalPosition.TOP}
as={Fragment} width={EModalWidth.XXXXL}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all w-full lg:max-w-4xl">
{issue && issue?.id ? ( {issue && issue?.id ? (
<div className="space-y-4">
<h3 className="text-xl font-medium text-custom-text-100">
Move {currentProjectDetails?.identifier}-{issue?.sequence_id} to project issues
</h3>
<InboxIssueEditRoot <InboxIssueEditRoot
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
@ -62,23 +33,9 @@ export const InboxIssueCreateEditModalRoot: FC<TInboxIssueCreateEditModalRoot> =
handleModalClose={handleModalClose} handleModalClose={handleModalClose}
onSubmit={onSubmit} onSubmit={onSubmit}
/> />
</div>
) : ( ) : (
<div className="space-y-4"> <InboxIssueCreateRoot workspaceSlug={workspaceSlug} projectId={projectId} handleModalClose={handleModalClose} />
<h3 className="text-xl font-medium text-custom-text-100">Create Inbox Issue</h3>
<InboxIssueCreateRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
handleModalClose={handleModalClose}
/>
</div>
)} )}
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</div>
); );
}); };

View File

@ -1,11 +1,9 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
import type { TIssue } from "@plane/types";
// icons
// ui
import { Button } from "@plane/ui";
// types // types
import type { TIssue } from "@plane/types";
// components
import { AlertModalCore } from "@/components/core";
// hooks
import { useProject } from "@/hooks/store"; import { useProject } from "@/hooks/store";
type Props = { type Props = {
@ -15,81 +13,46 @@ type Props = {
onSubmit: () => Promise<void>; onSubmit: () => Promise<void>;
}; };
export const DeclineIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSubmit }) => { export const DeclineIssueModal: React.FC<Props> = (props) => {
const { isOpen, onClose, data, onSubmit } = props;
// states
const [isDeclining, setIsDeclining] = useState(false); const [isDeclining, setIsDeclining] = useState(false);
// hooks // store hooks
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// derived values
const projectDetails = data.project_id ? getProjectById(data?.project_id) : undefined;
const handleClose = () => { const handleClose = () => {
setIsDeclining(false); setIsDeclining(false);
onClose(); onClose();
}; };
const handleDecline = () => { const handleDecline = async () => {
setIsDeclining(true); setIsDeclining(true);
onSubmit().finally(() => setIsDeclining(false)); await onSubmit().finally(() => setIsDeclining(false));
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDecline}
as={React.Fragment} isDeleting={isDeclining}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Decline Issue"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100" {" "}
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Decline Issue</h3>
</span>
</div>
<span>
<p className="text-sm text-custom-text-200">
Are you sure you want to decline issue{" "} Are you sure you want to decline issue{" "}
<span className="break-words font-medium text-custom-text-100"> <span className="break-words font-medium text-custom-text-100">
{(data && data?.project_id && getProjectById(data?.project_id)?.identifier) || ""}- {projectDetails?.identifier}-{data?.sequence_id}
{data?.sequence_id}
</span> </span>
{""}? This action cannot be undone. {""}? This action cannot be undone.
</p> </>
</span> }
<div className="flex justify-end gap-2"> primaryButtonText={{
<Button variant="neutral-primary" size="sm" onClick={handleClose}> loading: "Declining",
Cancel default: "Decline",
</Button> }}
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDecline} loading={isDeclining}> />
{isDeclining ? "Declining..." : "Decline Issue"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -1,14 +1,11 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
// icons
import type { TIssue } from "@plane/types";
// ui
import { Button } from "@plane/ui";
import { useProject } from "@/hooks/store";
// types // types
import type { TIssue } from "@plane/types";
// components
import { AlertModalCore } from "@/components/core";
// hooks
import { useProject } from "@/hooks/store";
type Props = { type Props = {
data: Partial<TIssue>; data: Partial<TIssue>;
@ -20,79 +17,37 @@ type Props = {
export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClose, onSubmit, data }) => { export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClose, onSubmit, data }) => {
// states // states
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
// store hooks
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// derived values
const projectDetails = data.project_id ? getProjectById(data?.project_id) : undefined;
const handleClose = () => { const handleClose = () => {
setIsDeleting(false); setIsDeleting(false);
onClose(); onClose();
}; };
const handleDelete = () => { const handleDelete = async () => {
setIsDeleting(true); setIsDeleting(true);
onSubmit().finally(() => handleClose()); await onSubmit().finally(() => handleClose());
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDelete}
as={React.Fragment} isDeleting={isDeleting}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete Issue"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Issue</h3>
</span>
</div>
<span>
<p className="text-sm text-custom-text-200">
Are you sure you want to delete issue{" "} Are you sure you want to delete issue{" "}
<span className="break-words font-medium text-custom-text-100"> <span className="break-words font-medium text-custom-text-100">
{(data && data?.project_id && getProjectById(data?.project_id)?.identifier) || ""}- {projectDetails?.identifier}-{data?.sequence_id}
{data?.sequence_id}
</span> </span>
{""}? The issue will only be deleted from the inbox and this action cannot be undone. {""}? The issue will only be deleted from the inbox and this action cannot be undone.
</p> </>
</span> }
<div className="flex justify-end gap-2"> />
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDelete} loading={isDeleting}>
{isDeleting ? "Deleting..." : "Delete Issue"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,10 +1,7 @@
import { FC, Fragment, useState } from "react"; import { FC, useState } from "react";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
import type { TIssueAttachment } from "@plane/types"; import type { TIssueAttachment } from "@plane/types";
// headless ui // components
// ui import { AlertModalCore } from "@/components/core";
import { Button } from "@plane/ui";
// helper // helper
import { getFileName } from "@/helpers/attachment.helper"; import { getFileName } from "@/helpers/attachment.helper";
// types // types
@ -35,74 +32,19 @@ export const IssueAttachmentDeleteModal: FC<Props> = (props) => {
}; };
return ( return (
data && ( <AlertModalCore
<Transition.Root show={isOpen} as={Fragment}> handleClose={handleClose}
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleSubmit={() => handleDeletion(data.id)}
<Transition.Child isDeleting={loader}
as={Fragment} isOpen={isOpen}
enter="ease-out duration-300" title="Delete attachment"
enterFrom="opacity-0" content={
enterTo="opacity-100" <>
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete attachment
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete attachment-{" "} Are you sure you want to delete attachment-{" "}
<span className="font-bold">{getFileName(data.attributes.name)}</span>? This attachment will <span className="font-bold">{getFileName(data.attributes.name)}</span>? This attachment will be permanently
be permanently removed. This action cannot be undone. removed. This action cannot be undone.
</p> </>
</div> }
</div> />
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={() => {
handleDeletion(data.id);
}}
disabled={loader}
>
{loader ? "Deleting" : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
)
); );
}; };

View File

@ -78,7 +78,7 @@ export const ConfirmIssueDiscard: React.FC<Props> = (props) => {
Cancel Cancel
</Button> </Button>
<Button variant="primary" size="sm" onClick={handleDeletion} loading={isLoading}> <Button variant="primary" size="sm" onClick={handleDeletion} loading={isLoading}>
{isLoading ? "Saving..." : "Save Draft"} {isLoading ? "Saving" : "Save draft"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,12 +1,12 @@
import { useEffect, useState, Fragment } from "react"; import { useEffect, useState } from "react";
import { AlertTriangle } from "lucide-react"; // types
import { Dialog, Transition } from "@headlessui/react";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// ui // ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// types // components
import { useIssues, useProject } from "@/hooks/store"; import { AlertModalCore } from "@/components/core";
// hooks // hooks
import { useIssues, useProject } from "@/hooks/store";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -18,12 +18,10 @@ type Props = {
export const DeleteIssueModal: React.FC<Props> = (props) => { export const DeleteIssueModal: React.FC<Props> = (props) => {
const { dataId, data, isOpen, handleClose, onSubmit } = props; const { dataId, data, isOpen, handleClose, onSubmit } = props;
// states
const { issueMap } = useIssues();
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
// store hooks
// hooks const { issueMap } = useIssues();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
useEffect(() => { useEffect(() => {
@ -32,7 +30,9 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
if (!dataId && !data) return null; if (!dataId && !data) return null;
// derived values
const issue = data ? data : issueMap[dataId!]; const issue = data ? data : issueMap[dataId!];
const projectDetails = getProjectById(issue?.project_id);
const onClose = () => { const onClose = () => {
setIsDeleting(false); setIsDeleting(false);
@ -57,65 +57,21 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={onClose}> handleClose={onClose}
<Transition.Child handleSubmit={handleIssueDelete}
as={Fragment} isDeleting={isDeleting}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete Issue"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<div className="grid place-items-center rounded-full bg-red-500/20 p-4">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Issue</h3>
</span>
</div>
<span>
<p className="text-sm text-custom-text-200">
Are you sure you want to delete issue{" "} Are you sure you want to delete issue{" "}
<span className="break-words font-medium text-custom-text-100"> <span className="break-words font-medium text-custom-text-100">
{getProjectById(issue?.project_id)?.identifier}-{issue?.sequence_id} {projectDetails?.identifier}-{issue?.sequence_id}
</span> </span>
{""}? All of the data related to the issue will be permanently removed. This action cannot be {""}? All of the data related to the issue will be permanently removed. This action cannot be undone.
undone. </>
</p> }
</span> />
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleIssueDelete} loading={isDeleting}>
{isDeleting ? "Deleting" : "Delete"}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -1,4 +1,4 @@
import React, { FC, useState, useRef, useEffect, Fragment } from "react"; import React, { FC, useState, useRef, useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
@ -296,8 +296,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
/> />
)} )}
<form onSubmit={handleSubmit((data) => handleFormSubmit(data))}> <form onSubmit={handleSubmit((data) => handleFormSubmit(data))}>
<div className="space-y-5"> <div className="space-y-5 p-5">
<div className="flex items-center gap-x-2"> <div className="flex items-center gap-x-3">
{/* Don't show project selection if editing an issue */} {/* Don't show project selection if editing an issue */}
{!data?.id && ( {!data?.id && (
<Controller <Controller
@ -322,16 +322,14 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
)} )}
/> />
)} )}
<h3 className="text-xl font-semibold leading-6 text-custom-text-100"> <h3 className="text-xl font-medium text-custom-text-200">{data?.id ? "Update" : "Create"} Issue</h3>
{data?.id ? "Update" : "Create"} issue
</h3>
</div> </div>
{watch("parent_id") && selectedParentIssue && ( {watch("parent_id") && selectedParentIssue && (
<Controller <Controller
control={control} control={control}
name="parent_id" name="parent_id"
render={({ field: { onChange } }) => ( render={({ field: { onChange } }) => (
<div className="flex w-min items-center gap-2 whitespace-nowrap rounded bg-custom-background-80 p-2 text-xs"> <div className="flex w-min items-center gap-2 whitespace-nowrap rounded bg-custom-background-90 p-2 text-xs">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
className="block h-1.5 w-1.5 rounded-full" className="block h-1.5 w-1.5 rounded-full"
@ -361,7 +359,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
/> />
)} )}
<div className="space-y-3"> <div className="space-y-3">
<div className="mt-2 space-y-3"> <div className="space-y-1">
<Controller <Controller
control={control} control={control}
name="name" name="name"
@ -384,15 +382,15 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
}} }}
ref={issueTitleRef || ref} ref={issueTitleRef || ref}
hasError={Boolean(errors.name)} hasError={Boolean(errors.name)}
placeholder="Issue Title" placeholder="Title"
className="w-full resize-none text-xl" className="w-full text-base"
tabIndex={getTabIndex("name")} tabIndex={getTabIndex("name")}
autoFocus autoFocus
/> />
)} )}
/> />
<span className="text-xs text-red-500">{errors?.name?.message}</span> <span className="text-xs text-red-500">{errors?.name?.message}</span>
</div>
<div className="relative"> <div className="relative">
{data?.description_html === undefined ? ( {data?.description_html === undefined ? (
<Loader className="min-h-[7rem] space-y-2 overflow-hidden rounded-md border border-custom-border-200 p-2 py-2"> <Loader className="min-h-[7rem] space-y-2 overflow-hidden rounded-md border border-custom-border-200 p-2 py-2">
@ -415,12 +413,12 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
</div> </div>
</Loader> </Loader>
) : ( ) : (
<Fragment> <>
<div className="border-0.5 absolute bottom-3.5 right-3.5 z-10 flex items-center gap-2"> <div className="border-0.5 absolute bottom-3.5 right-3.5 z-10 flex items-center gap-2">
{issueName && issueName.trim() !== "" && envConfig?.has_openai_configured && ( {issueName && issueName.trim() !== "" && envConfig?.has_openai_configured && (
<button <button
type="button" type="button"
className={`flex items-center gap-1 rounded bg-custom-background-80 px-1.5 py-1 text-xs ${ className={`flex items-center gap-1 rounded bg-custom-background-90 px-1.5 py-1 text-xs ${
iAmFeelingLucky ? "cursor-wait" : "" iAmFeelingLucky ? "cursor-wait" : ""
}`} }`}
onClick={handleAutoGenerateDescription} onClick={handleAutoGenerateDescription}
@ -484,7 +482,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
/> />
)} )}
/> />
</Fragment> </>
)} )}
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
@ -727,33 +725,29 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
</div> </div>
</div> </div>
</div> </div>
</div> <div className="px-5 py-4 flex items-center justify-between gap-2 border-t-[0.5px] border-custom-border-200">
<div className="-mx-5 mt-5 flex items-center justify-between gap-2 border-t border-custom-border-100 px-5 pt-5">
<div> <div>
{!data?.id && ( {!data?.id && (
<div <div
className="inline-flex cursor-default items-center gap-1.5" className="inline-flex items-center gap-1.5 cursor-pointer"
onClick={() => onCreateMoreToggleChange(!isCreateMoreToggleEnabled)} onClick={() => onCreateMoreToggleChange(!isCreateMoreToggleEnabled)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") onCreateMoreToggleChange(!isCreateMoreToggleEnabled); if (e.key === "Enter") onCreateMoreToggleChange(!isCreateMoreToggleEnabled);
}} }}
tabIndex={getTabIndex("create_more")} tabIndex={getTabIndex("create_more")}
role="button"
> >
<div className="flex cursor-pointer items-center justify-center">
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" /> <ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
</div>
<span className="text-xs">Create more</span> <span className="text-xs">Create more</span>
</div> </div>
)} )}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={onClose} tabIndex={getTabIndex("discard_button")}> <Button variant="neutral-primary" size="sm" onClick={onClose} tabIndex={getTabIndex("discard_button")}>
Discard Discard
</Button> </Button>
{isDraft && ( {isDraft && (
<Fragment> <>
{data?.id ? ( {data?.id ? (
<Button <Button
variant="neutral-primary" variant="neutral-primary"
@ -775,9 +769,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
{isSubmitting ? "Saving" : "Save as draft"} {isSubmitting ? "Saving" : "Save as draft"}
</Button> </Button>
)} )}
</Fragment> </>
)} )}
<Button <Button
variant="primary" variant="primary"
type="submit" type="submit"
@ -785,7 +778,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
loading={isSubmitting} loading={isSubmitting}
tabIndex={isDraft ? getTabIndex("submit_button") : getTabIndex("draft_button")} tabIndex={isDraft ? getTabIndex("submit_button") : getTabIndex("draft_button")}
> >
{data?.id ? (isSubmitting ? "Updating" : "Update issue") : isSubmitting ? "Creating" : "Create issue"} {data?.id ? (isSubmitting ? "Updating" : "Update Issue") : isSubmitting ? "Creating" : "Create Issue"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,13 +1,16 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react"; // types
import type { TIssue } from "@plane/types"; import type { TIssue } from "@plane/types";
// hooks // ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// constants
import { ISSUE_CREATED, ISSUE_UPDATED } from "@/constants/event-tracker"; import { ISSUE_CREATED, ISSUE_UPDATED } from "@/constants/event-tracker";
import { EIssuesStoreType } from "@/constants/issue"; import { EIssuesStoreType } from "@/constants/issue";
// hooks
import { import {
useApplication, useApplication,
useEventTracker, useEventTracker,
@ -22,9 +25,6 @@ import useLocalStorage from "@/hooks/use-local-storage";
// components // components
import { DraftIssueLayout } from "./draft-issue-layout"; import { DraftIssueLayout } from "./draft-issue-layout";
import { IssueFormRoot } from "./form"; import { IssueFormRoot } from "./form";
// ui
// types
// constants
export interface IssuesModalProps { export interface IssuesModalProps {
data?: Partial<TIssue>; data?: Partial<TIssue>;
@ -241,32 +241,12 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
if (!workspaceProjectIds || workspaceProjectIds.length === 0 || !activeProjectId) return null; if (!workspaceProjectIds || workspaceProjectIds.length === 0 || !activeProjectId) return null;
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <ModalCore
<Dialog as="div" className="relative z-20" onClose={() => handleClose(true)}> isOpen={isOpen}
<Transition.Child handleClose={() => handleClose(true)}
as={React.Fragment} position={EModalPosition.TOP}
enter="ease-out duration-300" width={EModalWidth.XXXXL}
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative mx-4 transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-4xl">
{withDraftIssueWrapper ? ( {withDraftIssueWrapper ? (
<DraftIssueLayout <DraftIssueLayout
changesMade={changesMade} changesMade={changesMade}
@ -302,11 +282,6 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
isDraft={isDraft} isDraft={isDraft}
/> />
)} )}
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,15 +1,14 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
import type { IIssueLabel } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { useLabel } from "@/hooks/store";
// icons
// ui
// types // types
import type { IIssueLabel } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// hooks
import { useLabel } from "@/hooks/store";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -54,64 +53,18 @@ export const DeleteLabelModal: React.FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDeletion}
as={React.Fragment} isDeleting={isDeleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete Label"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100" Are you sure you want to delete <span className="font-medium text-custom-text-100">{data?.name}</span>? This
leaveTo="opacity-0" will remove the label from all the issue and from any views where the label is being filtered upon.
> </>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" /> }
</Transition.Child> />
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete Label
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you wish to delete{" "}
<span className="font-medium text-custom-text-100">{data?.name}</span>? This will remove the
label from all the issue and from any views where the label is being filtered upon.
</p>
</div>
</div>
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,17 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
import type { IModule } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { MODULE_DELETED } from "@/constants/event-tracker";
import { useEventTracker, useModule } from "@/hooks/store";
// ui
// icons
// types // types
import type { IModule } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// constants // constants
import { MODULE_DELETED } from "@/constants/event-tracker";
// hooks
import { useEventTracker, useModule } from "@/hooks/store";
type Props = { type Props = {
data: IModule; data: IModule;
@ -71,64 +70,19 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDeletion}
as={React.Fragment} isDeleting={isDeleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete Module"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete Module
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete module-{" "} Are you sure you want to delete module-{" "}
<span className="break-all font-medium text-custom-text-100">{data?.name}</span>? All of the <span className="break-all font-medium text-custom-text-100">{data?.name}</span>? All of the data related to
data related to the module will be permanently removed. This action cannot be undone. the module will be permanently removed. This action cannot be undone.
</p> </>
</div> }
</div> />
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -64,7 +64,7 @@ export const ModuleForm: React.FC<Props> = (props) => {
return ( return (
<form onSubmit={handleSubmit(handleCreateUpdateModule)}> <form onSubmit={handleSubmit(handleCreateUpdateModule)}>
<div className="space-y-5"> <div className="space-y-5 p-5">
<div className="flex items-center gap-x-3"> <div className="flex items-center gap-x-3">
{!status && ( {!status && (
<Controller <Controller
@ -86,11 +86,10 @@ export const ModuleForm: React.FC<Props> = (props) => {
)} )}
/> />
)} )}
<h3 className="text-xl font-medium leading-6 text-custom-text-200">{status ? "Update" : "New"} Module</h3> <h3 className="text-xl font-medium text-custom-text-200">{status ? "Update" : "Create"} Module</h3>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<div className="flex flex-col gap-1"> <div className="space-y-1">
<Controller <Controller
control={control} control={control}
name="name" name="name"
@ -101,18 +100,18 @@ export const ModuleForm: React.FC<Props> = (props) => {
message: "Title should be less than 255 characters", message: "Title should be less than 255 characters",
}, },
}} }}
render={({ field: { value, onChange, ref } }) => ( render={({ field: { value, onChange } }) => (
<Input <Input
id="name" id="name"
name="name" name="name"
type="text" type="text"
value={value} value={value}
onChange={onChange} onChange={onChange}
ref={ref}
hasError={Boolean(errors?.name)} hasError={Boolean(errors?.name)}
placeholder="Module Title" placeholder="Title"
className="w-full resize-none placeholder:text-sm placeholder:font-medium focus:border-blue-400" className="w-full text-base"
tabIndex={1} tabIndex={1}
autoFocus
/> />
)} )}
/> />
@ -128,8 +127,8 @@ export const ModuleForm: React.FC<Props> = (props) => {
name="description" name="description"
value={value} value={value}
onChange={onChange} onChange={onChange}
placeholder="Description..." placeholder="Description"
className="w-full text-sm resize-none min-h-24" className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
tabIndex={2} tabIndex={2}
/> />
@ -211,12 +210,12 @@ export const ModuleForm: React.FC<Props> = (props) => {
</div> </div>
</div> </div>
</div> </div>
<div className="mt-5 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200 pt-5"> <div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={7}> <Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={7}>
Cancel Cancel
</Button> </Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={8}> <Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={8}>
{status ? (isSubmitting ? "Updating" : "Update module") : isSubmitting ? "Creating" : "Create module"} {status ? (isSubmitting ? "Updating" : "Update Module") : isSubmitting ? "Creating" : "Create Module"}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -1,17 +1,17 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react"; // types
import type { IModule } from "@plane/types"; import type { IModule } from "@plane/types";
// components // ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { ModuleForm } from "@/components/modules"; import { ModuleForm } from "@/components/modules";
// constants
import { MODULE_CREATED, MODULE_UPDATED } from "@/constants/event-tracker"; import { MODULE_CREATED, MODULE_UPDATED } from "@/constants/event-tracker";
// hooks // hooks
import { useEventTracker, useModule, useProject } from "@/hooks/store"; import { useEventTracker, useModule, useProject } from "@/hooks/store";
// ui
// components
// types
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -140,32 +140,7 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
}, [activeProject, data, projectId, workspaceProjectIds, isOpen]); }, [activeProject, data, projectId, workspaceProjectIds, isOpen]);
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<ModuleForm <ModuleForm
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
handleClose={handleClose} handleClose={handleClose}
@ -174,11 +149,6 @@ export const CreateUpdateModuleModal: React.FC<Props> = observer((props) => {
setActiveProject={setActiveProject} setActiveProject={setActiveProject}
data={data} data={data}
/> />
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,9 +1,9 @@
import { FC, Fragment, useState } from "react"; import { FC, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
// types // types
import { TPage } from "@plane/types"; import { TPage } from "@plane/types";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { PageForm } from "@/components/pages"; import { PageForm } from "@/components/pages";
// constants // constants
import { PAGE_CREATED } from "@/constants/event-tracker"; import { PAGE_CREATED } from "@/constants/event-tracker";
@ -67,43 +67,18 @@ export const CreatePageModal: FC<Props> = (props) => {
}; };
return ( return (
<Transition.Root show={isModalOpen} as={Fragment}> <ModalCore
<Dialog as="div" className="relative z-20" onClose={handleModalClose}> isOpen={isModalOpen}
<Transition.Child handleClose={handleModalClose}
as={Fragment} position={EModalPosition.TOP}
enter="ease-out duration-300" width={EModalWidth.XXL}
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="my-10 flex justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 px-4 text-left shadow-custom-shadow-md transition-all w-full sm:max-w-2xl">
<PageForm <PageForm
formData={pageFormData} formData={pageFormData}
handleFormData={handlePageFormData} handleFormData={handlePageFormData}
handleModalClose={handleStateClear} handleModalClose={handleStateClear}
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
/> />
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -1,9 +1,9 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// ui // ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// constants // constants
import { PAGE_DELETED } from "@/constants/event-tracker"; import { PAGE_DELETED } from "@/constants/event-tracker";
// hooks // hooks
@ -71,64 +71,19 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDelete}
as={React.Fragment} isDeleting={isDeleting}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete Page"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete Page
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete page-{" "} Are you sure you want to delete page-{" "}
<span className="break-words font-medium text-custom-text-100">{name}</span>? The Page will be <span className="break-words font-medium text-custom-text-100">{name}</span>? The Page will be deleted
deleted permanently. This action cannot be undone. permanently. This action cannot be undone.
</p> </>
</div> }
</div> />
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDelete} loading={isDeleting}>
{isDeleting ? "Deleting..." : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -39,28 +39,27 @@ export const PageForm: React.FC<Props> = (props) => {
return ( return (
<form onSubmit={handlePageFormSubmit}> <form onSubmit={handlePageFormSubmit}>
<div className="space-y-4"> <div className="space-y-5 p-5">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">Create Page</h3> <h3 className="text-xl font-medium text-custom-text-200">Create Page</h3>
<div className="space-y-1">
<div className="space-y-2">
<Input <Input
id="name" id="name"
type="text" type="text"
value={formData.name} value={formData.name}
onChange={(e) => handleFormData("name", e.target.value)} onChange={(e) => handleFormData("name", e.target.value)}
placeholder="Title" placeholder="Title"
className="w-full resize-none text-lg" className="w-full resize-none text-base"
tabIndex={1} tabIndex={1}
required required
autoFocus
/> />
{isTitleLengthMoreThan255Character && ( {isTitleLengthMoreThan255Character && (
<span className="text-xs text-red-500">Max length of the name should be less than 255 characters</span> <span className="text-xs text-red-500">Max length of the name should be less than 255 characters</span>
)} )}
</div> </div>
</div> </div>
<div className="px-5 py-4 flex items-center justify-between gap-2 border-t-[0.5px] border-custom-border-200">
<div className="mt-5 relative flex items-center justify-between gap-2"> <div className="flex items-center gap-2">
<div className="relative flex items-center gap-2">
<div className="flex flex-shrink-0 items-stretch gap-0.5 rounded border-[0.5px] border-custom-border-200 p-1"> <div className="flex flex-shrink-0 items-stretch gap-0.5 rounded border-[0.5px] border-custom-border-200 p-1">
{PAGE_ACCESS_SPECIFIERS.map((access, index) => ( {PAGE_ACCESS_SPECIFIERS.map((access, index) => (
<Tooltip key={access.key} tooltipContent={access.label} isMobile={isMobile}> <Tooltip key={access.key} tooltipContent={access.label} isMobile={isMobile}>
@ -88,8 +87,7 @@ export const PageForm: React.FC<Props> = (props) => {
{PAGE_ACCESS_SPECIFIERS.find((access) => access.key === formData.access)?.label} {PAGE_ACCESS_SPECIFIERS.find((access) => access.key === formData.access)?.label}
</h6> </h6>
</div> </div>
<div className="flex items-center justify-end gap-2">
<div className="relative flex items-center gap-2 justify-end">
<Button variant="neutral-primary" size="sm" onClick={handleModalClose} tabIndex={4}> <Button variant="neutral-primary" size="sm" onClick={handleModalClose} tabIndex={4}>
Cancel Cancel
</Button> </Button>

View File

@ -1,16 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react"; // types
import { Dialog, Transition } from "@headlessui/react";
import type { IState } from "@plane/types"; import type { IState } from "@plane/types";
// ui // ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// constants // constants
import { STATE_DELETED } from "@/constants/event-tracker"; import { STATE_DELETED } from "@/constants/event-tracker";
// hooks // hooks
import { useEventTracker, useProjectState } from "@/hooks/store"; import { useEventTracker, useProjectState } from "@/hooks/store";
// types
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -78,64 +78,18 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDeletion}
as={React.Fragment} isDeleting={isDeleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete State"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100" Are you sure you want to delete state- <span className="font-medium text-custom-text-100">{data?.name}</span>?
leaveTo="opacity-0" All of the data related to the state will be permanently removed. This action cannot be undone.
> </>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" /> }
</Transition.Child> />
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="bg-custom-background-100 px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete State
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete state-{" "}
<span className="font-medium text-custom-text-100">{data?.name}</span>? All of the data
related to the state will be permanently removed. This action cannot be undone.
</p>
</div>
</div>
</div>
</div>
<div className="flex justify-end gap-2 bg-custom-background-100 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,14 +1,14 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react"; // types
import { Dialog, Transition } from "@headlessui/react";
import { IProjectView } from "@plane/types"; import { IProjectView } from "@plane/types";
// ui // ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// hooks // hooks
import { useProjectView } from "@/hooks/store"; import { useProjectView } from "@/hooks/store";
// types
type Props = { type Props = {
data: IProjectView; data: IProjectView;
@ -59,64 +59,19 @@ export const DeleteProjectViewModal: React.FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDeleteView}
as={React.Fragment} isDeleting={isDeleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete View"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete View
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete view-{" "} Are you sure you want to delete view-{" "}
<span className="break-all font-medium text-custom-text-100">{data?.name}</span>? All of the <span className="break-all font-medium text-custom-text-100">{data?.name}</span>? All of the data related to
data related to the view will be permanently removed. This action cannot be undone. the view will be permanently removed. This action cannot be undone.
</p> </>
</div> }
</div> />
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDeleteView}>
{isDeleteLoading ? "Deleting..." : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,16 +1,16 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { IProjectView, IIssueFilterOptions } from "@plane/types";
// hooks
import { Button, Input, TextArea } from "@plane/ui";
import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "@/components/issues";
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
// components
// ui
// types // types
import { IProjectView, IIssueFilterOptions } from "@plane/types";
// ui
import { Button, Input, TextArea } from "@plane/ui";
// components
import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// hooks
import { useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
type Props = { type Props = {
data?: IProjectView | null; data?: IProjectView | null;
@ -109,10 +109,10 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
return ( return (
<form onSubmit={handleSubmit(handleCreateUpdateView)}> <form onSubmit={handleSubmit(handleCreateUpdateView)}>
<div className="space-y-5"> <div className="space-y-5 p-5">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">{data ? "Update" : "Create"} View</h3> <h3 className="text-xl font-medium text-custom-text-200">{data ? "Update" : "Create"} View</h3>
<div className="space-y-3"> <div className="space-y-3">
<div className="flex flex-col gap-1"> <div className="space-y-1">
<Controller <Controller
control={control} control={control}
name="name" name="name"
@ -132,8 +132,9 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
onChange={onChange} onChange={onChange}
hasError={Boolean(errors.name)} hasError={Boolean(errors.name)}
placeholder="Title" placeholder="Title"
className="w-full resize-none text-xl focus:border-blue-400" className="w-full text-base"
tabIndex={1} tabIndex={1}
autoFocus
/> />
)} )}
/> />
@ -148,7 +149,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
id="description" id="description"
name="description" name="description"
placeholder="Description" placeholder="Description"
className="min-h-24 w-full resize-none text-sm" className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
value={value} value={value}
onChange={onChange} onChange={onChange}
@ -206,18 +207,12 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
)} )}
</div> </div>
</div> </div>
<div className="mt-5 flex justify-end gap-2"> <div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}> <Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}>
Cancel Cancel
</Button> </Button>
<Button variant="primary" size="sm" type="submit" tabIndex={5} disabled={isSubmitting}> <Button variant="primary" size="sm" type="submit" tabIndex={5} loading={isSubmitting}>
{data {data ? (isSubmitting ? "Updating" : "Update View") : isSubmitting ? "Creating" : "Create View"}
? isSubmitting
? "Updating View..."
: "Update View"
: isSubmitting
? "Creating View..."
: "Create View"}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -1,14 +1,14 @@
import { FC, Fragment } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Dialog, Transition } from "@headlessui/react"; // types
import { IProjectView } from "@plane/types"; import { IProjectView } from "@plane/types";
// ui // ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { ProjectViewForm } from "@/components/views"; import { ProjectViewForm } from "@/components/views";
// hooks // hooks
import { useProjectView } from "@/hooks/store"; import { useProjectView } from "@/hooks/store";
// types
type Props = { type Props = {
data?: IProjectView | null; data?: IProjectView | null;
@ -65,43 +65,13 @@ export const CreateUpdateProjectViewModal: FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={Fragment}> <ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<ProjectViewForm <ProjectViewForm
data={data} data={data}
handleClose={handleClose} handleClose={handleClose}
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
preLoadedData={preLoadedData} preLoadedData={preLoadedData}
/> />
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,18 +1,18 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// ui // types
import { Dialog, Transition } from "@headlessui/react";
import { IWebhook, IWorkspace, TWebhookEventTypes } from "@plane/types"; import { IWebhook, IWorkspace, TWebhookEventTypes } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// helpers // helpers
import { csvDownload } from "@/helpers/download.helper"; import { csvDownload } from "@/helpers/download.helper";
// types // components
import { WebhookForm } from "./form"; import { WebhookForm } from "./form";
import { GeneratedHookDetails } from "./generated-hook-details"; import { GeneratedHookDetails } from "./generated-hook-details";
// utils // utils
import { getCurrentHookAsCSV } from "./utils"; import { getCurrentHookAsCSV } from "./utils";
// ui
interface ICreateWebhookModal { interface ICreateWebhookModal {
currentWorkspace: IWorkspace | null; currentWorkspace: IWorkspace | null;
@ -93,48 +93,19 @@ export const CreateWebhookModal: React.FC<ICreateWebhookModal> = (props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <ModalCore
<Dialog isOpen={isOpen}
as="div" handleClose={() => {
className="relative z-20"
onClose={() => {
if (!generatedWebhook) handleClose(); if (!generatedWebhook) handleClose();
}} }}
position={EModalPosition.TOP}
width={EModalWidth.XXL}
> >
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 p-6 text-left shadow-xl transition-all sm:my-8 w-full sm:max-w-2xl">
{!generatedWebhook ? ( {!generatedWebhook ? (
<WebhookForm onSubmit={handleCreateWebhook} handleClose={handleClose} /> <WebhookForm onSubmit={handleCreateWebhook} handleClose={handleClose} />
) : ( ) : (
<GeneratedHookDetails webhookDetails={generatedWebhook} handleClose={handleClose} /> <GeneratedHookDetails webhookDetails={generatedWebhook} handleClose={handleClose} />
)} )}
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -1,9 +1,9 @@
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// ui // ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// hooks // hooks
import { useWebhook } from "@/hooks/store"; import { useWebhook } from "@/hooks/store";
@ -52,59 +52,18 @@ export const DeleteWebhookModal: FC<IDeleteWebhook> = (props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDelete}
as={React.Fragment} isDeleting={isDeleting}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete webhook"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100" Are you sure you want to delete this webhook? Future events will not be delivered to this webhook. This action
leaveTo="opacity-0" cannot be undone.
> </>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" /> }
</Transition.Child> />
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 p-6 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete webhook</h3>
</span>
</div>
<p className="mt-4 text-sm text-custom-text-200">
Are you sure you want to delete this webhook? Future events will not be delivered to this webhook.
This action cannot be undone.
</p>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDelete} loading={isDeleting}>
{isDeleting ? "Deleting..." : "Delete webhook"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}; };

View File

@ -58,11 +58,11 @@ export const WebhookForm: FC<Props> = observer((props) => {
}, [data]); }, [data]);
return ( return (
<div className="space-y-6">
<div className="text-xl font-medium">{data ? "Webhook details" : "Create webhook"}</div>
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="space-y-8"> <div className="space-y-5 p-5">
<div> <div className="text-xl font-medium text-custom-text-200">{data ? "Webhook details" : "Create webhook"}</div>
<div className="space-y-3">
<div className="space-y-1">
<Controller <Controller
control={control} control={control}
name="url" name="url"
@ -76,34 +76,31 @@ export const WebhookForm: FC<Props> = observer((props) => {
{errors.url && <div className="text-xs text-red-500">{errors.url.message}</div>} {errors.url && <div className="text-xs text-red-500">{errors.url.message}</div>}
</div> </div>
{data && <WebhookToggle control={control} />} {data && <WebhookToggle control={control} />}
<div className="space-y-3">
<WebhookOptions value={webhookEventType} onChange={(val) => setWebhookEventType(val)} /> <WebhookOptions value={webhookEventType} onChange={(val) => setWebhookEventType(val)} />
</div> </div>
</div>
<div className="mt-4"> <div className="mt-4">
{webhookEventType === "individual" && <WebhookIndividualEventOptions control={control} />} {webhookEventType === "individual" && <WebhookIndividualEventOptions control={control} />}
</div> </div>
</div>
{data ? ( {data ? (
<div className="mt-8 space-y-8"> <div className="p-5 pt-0 space-y-5">
<WebhookSecretKey data={data} /> <WebhookSecretKey data={data} />
<Button type="submit" loading={isSubmitting}> <Button type="submit" loading={isSubmitting}>
{isSubmitting ? "Updating..." : "Update"} {isSubmitting ? "Updating" : "Update"}
</Button> </Button>
</div> </div>
) : ( ) : (
<div className="mt-4 flex justify-end gap-2"> <div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" onClick={handleClose}> <Button variant="neutral-primary" size="sm" onClick={handleClose}>
Discard Cancel
</Button> </Button>
{!webhookSecretKey && ( {!webhookSecretKey && (
<Button type="submit" variant="primary" loading={isSubmitting}> <Button type="submit" variant="primary" size="sm" loading={isSubmitting}>
{isSubmitting ? "Creating..." : "Create"} {isSubmitting ? "Creating" : "Create"}
</Button> </Button>
)} )}
</div> </div>
)} )}
</form> </form>
</div>
); );
}); });

View File

@ -14,20 +14,22 @@ export const GeneratedHookDetails: React.FC<Props> = (props) => {
const { handleClose, webhookDetails } = props; const { handleClose, webhookDetails } = props;
return ( return (
<div> <>
<div className="space-y-3 mb-3"> <div className="space-y-5 p-5">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">Key created</h3> <div className="space-y-3">
<h3 className="text-xl font-medium text-custom-text-200">Key created</h3>
<p className="text-sm text-custom-text-400"> <p className="text-sm text-custom-text-400">
Copy and save this secret key in Plane Pages. You can{"'"}t see this key after you hit Close. A CSV file Copy and save this secret key in Plane Pages. You can{"'"}t see this key after you hit Close. A CSV file
containing the key has been downloaded. containing the key has been downloaded.
</p> </p>
</div> </div>
<WebhookSecretKey data={webhookDetails} /> <WebhookSecretKey data={webhookDetails} />
<div className="mt-6 flex justify-end"> </div>
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose}> <Button variant="neutral-primary" size="sm" onClick={handleClose}>
Close Close
</Button> </Button>
</div> </div>
</div> </>
); );
}; };

View File

@ -1,19 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// ui // types
import { AlertTriangle } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// icons
import { IWorkspaceView } from "@plane/types"; import { IWorkspaceView } from "@plane/types";
// ui // ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// constants // constants
import { GLOBAL_VIEW_DELETED } from "@/constants/event-tracker"; import { GLOBAL_VIEW_DELETED } from "@/constants/event-tracker";
// store hooks // hooks
import { useGlobalView, useEventTracker } from "@/hooks/store"; import { useGlobalView, useEventTracker } from "@/hooks/store";
// ui
// types
type Props = { type Props = {
data: IWorkspaceView; data: IWorkspaceView;
@ -32,9 +29,7 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
const { deleteGlobalView } = useGlobalView(); const { deleteGlobalView } = useGlobalView();
const { captureEvent } = useEventTracker(); const { captureEvent } = useEventTracker();
const handleClose = () => { const handleClose = () => onClose();
onClose();
};
const handleDeletion = async () => { const handleDeletion = async () => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -69,64 +64,19 @@ export const DeleteGlobalViewModal: React.FC<Props> = observer((props) => {
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <AlertModalCore
<Dialog as="div" className="relative z-20" onClose={handleClose}> handleClose={handleClose}
<Transition.Child handleSubmit={handleDeletion}
as={React.Fragment} isDeleting={isDeleteLoading}
enter="ease-out duration-300" isOpen={isOpen}
enterFrom="opacity-0" title="Delete View"
enterTo="opacity-100" content={
leave="ease-in duration-200" <>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete View
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete view-{" "} Are you sure you want to delete view-{" "}
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>? All of the <span className="break-words font-medium text-custom-text-100">{data?.name}</span>? All of the data related to
data related to the view will be permanently removed. This action cannot be undone. the view will be permanently removed. This action cannot be undone.
</p> </>
</div> }
</div> />
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete"}
</Button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -1,6 +1,7 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// types
import { IIssueFilterOptions, IWorkspaceView } from "@plane/types"; import { IIssueFilterOptions, IWorkspaceView } from "@plane/types";
// ui // ui
import { Button, Input, TextArea } from "@plane/ui"; import { Button, Input, TextArea } from "@plane/ui";
@ -10,7 +11,6 @@ import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "@/componen
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// hooks // hooks
import { useLabel, useMember } from "@/hooks/store"; import { useLabel, useMember } from "@/hooks/store";
// types
type Props = { type Props = {
handleFormSubmit: (values: Partial<IWorkspaceView>) => Promise<void>; handleFormSubmit: (values: Partial<IWorkspaceView>) => Promise<void>;
@ -97,10 +97,10 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
return ( return (
<form onSubmit={handleSubmit(handleCreateUpdateView)}> <form onSubmit={handleSubmit(handleCreateUpdateView)}>
<div className="space-y-5"> <div className="space-y-5 p-5">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">{data ? "Update" : "Create"} View</h3> <h3 className="text-xl font-medium text-custom-text-200">{data ? "Update" : "Create"} View</h3>
<div className="space-y-3"> <div className="space-y-3">
<div> <div className="space-y-1">
<Controller <Controller
control={control} control={control}
name="name" name="name"
@ -121,10 +121,11 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
ref={ref} ref={ref}
hasError={Boolean(errors.name)} hasError={Boolean(errors.name)}
placeholder="Title" placeholder="Title"
className="w-full resize-none text-xl" className="w-full text-base"
/> />
)} )}
/> />
<span className="text-xs text-red-500">{errors?.name?.message}</span>
</div> </div>
<div> <div>
<Controller <Controller
@ -137,7 +138,7 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
value={value} value={value}
placeholder="Description" placeholder="Description"
onChange={onChange} onChange={onChange}
className="min-h-24 w-full resize-none text-sm" className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
/> />
)} )}
@ -190,18 +191,12 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
)} )}
</div> </div>
</div> </div>
<div className="mt-5 flex justify-end gap-2"> <div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose}> <Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel Cancel
</Button> </Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}> <Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{data {data ? (isSubmitting ? "Updating" : "Update View") : isSubmitting ? "Creating" : "Create View"}
? isSubmitting
? "Updating View..."
: "Update View"
: isSubmitting
? "Creating View..."
: "Create View"}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -1,17 +1,17 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react"; // types
import { IWorkspaceView } from "@plane/types"; import { IWorkspaceView } from "@plane/types";
// ui // ui
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
import { WorkspaceViewForm } from "@/components/workspace"; import { WorkspaceViewForm } from "@/components/workspace";
// constants // constants
import { GLOBAL_VIEW_CREATED, GLOBAL_VIEW_UPDATED } from "@/constants/event-tracker"; import { GLOBAL_VIEW_CREATED, GLOBAL_VIEW_UPDATED } from "@/constants/event-tracker";
// store hooks // store hooks
import { useEventTracker, useGlobalView } from "@/hooks/store"; import { useEventTracker, useGlobalView } from "@/hooks/store";
// types
type Props = { type Props = {
data?: IWorkspaceView; data?: IWorkspaceView;
@ -120,43 +120,13 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
}; };
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<WorkspaceViewForm <WorkspaceViewForm
handleFormSubmit={handleFormSubmit} handleFormSubmit={handleFormSubmit}
handleClose={handleClose} handleClose={handleClose}
data={data} data={data}
preLoadedData={preLoadedData} preLoadedData={preLoadedData}
/> />
</Dialog.Panel> </ModalCore>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
); );
}); });

View File

@ -96,7 +96,9 @@ const WebhookDetailsPage: NextPageWithLayout = observer(() => {
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} /> <DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} />
<div className="w-full space-y-8 overflow-y-auto md:py-8 py-4 md:pr-9 pr-4"> <div className="w-full space-y-8 overflow-y-auto md:py-8 py-4 md:pr-9 pr-4">
<div className="-m-5">
<WebhookForm onSubmit={async (data) => await handleUpdateWebhook(data)} data={currentWebhook} /> <WebhookForm onSubmit={async (data) => await handleUpdateWebhook(data)} data={currentWebhook} />
</div>
{currentWebhook && <WebhookDeleteSection openDeleteModal={() => setDeleteWebhookModal(true)} />} {currentWebhook && <WebhookDeleteSection openDeleteModal={() => setDeleteWebhookModal(true)} />}
</div> </div>
</> </>