fix: removed unused packages and upgraded to next 14 (#2944)
* fix: upgrading next package and removed unused deps * chore: unused variable removed * chore: next image icon fix * chore: unused component removed * chore: next image icon fix * chore: replace use-debounce with lodash debounce * chore: unused component removed * resolved: fixed issue with next link component * fix: updates in next config * fix: updating types pages --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
parent
804313413b
commit
ee30eb0590
137 changed files with 469 additions and 1524 deletions
|
|
@ -114,7 +114,9 @@ export const EmailSignUpForm: React.FC<Props> = (props) => {
|
|||
</div>
|
||||
<div className="text-right text-xs">
|
||||
<Link href="/">
|
||||
<a className="text-custom-text-200 hover:text-custom-primary-100">Already have an account? Sign in.</a>
|
||||
<span className="text-custom-text-200 hover:text-custom-primary-100">
|
||||
Already have an account? Sign in.
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
import { BarDatum } from "@nivo/bar";
|
||||
// components
|
||||
import { CustomTooltip } from "./custom-tooltip";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// ui
|
||||
import { BarGraph, Tooltip } from "components/ui";
|
||||
import { BarGraph } from "components/ui";
|
||||
// helpers
|
||||
import { findStringWithMostCharacters } from "helpers/array.helper";
|
||||
import { generateBarColor, generateDisplayName } from "helpers/analytics.helper";
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
|||
<p>
|
||||
You have signed in as {user.email}. <br />
|
||||
<Link href={`/?next=${currentPath}`}>
|
||||
<a className="font-medium text-custom-text-100">Sign in</a>
|
||||
<span className="font-medium text-custom-text-100">Sign in</span>
|
||||
</Link>{" "}
|
||||
with different account that has access to this page.
|
||||
</p>
|
||||
|
|
@ -46,7 +46,7 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
|||
<p>
|
||||
You need to{" "}
|
||||
<Link href={`/?next=${currentPath}`}>
|
||||
<a className="font-medium text-custom-text-100">Sign in</a>
|
||||
<span className="font-medium text-custom-text-100">Sign in</span>
|
||||
</Link>{" "}
|
||||
with an account that has access to this page.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ export const NotAWorkspaceMember = () => (
|
|||
</div>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Link href="/invitations">
|
||||
<a>
|
||||
<span>
|
||||
<Button variant="neutral-primary">Check pending invites</Button>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
<Link href="/create-workspace">
|
||||
<a>
|
||||
<span>
|
||||
<Button variant="primary">Create new workspace</Button>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({
|
|||
<>
|
||||
{link ? (
|
||||
<Link href={link}>
|
||||
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm ${linkTruncate ? "truncate" : ""}`}>
|
||||
<span className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm ${linkTruncate ? "truncate" : ""}`}>
|
||||
<p className={`${linkTruncate ? "truncate" : ""}${icon ? "flex items-center gap-2" : ""}`}>
|
||||
{icon ?? null}
|
||||
{title}
|
||||
</p>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div className={`px-3 text-sm truncate ${unshrinkTitle ? "flex-shrink-0" : ""}`}>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const EmptyState: React.FC<Props> = ({
|
|||
}) => (
|
||||
<div className={`flex items-center justify-center h-full w-full`}>
|
||||
<div className="text-center flex flex-col items-center w-full">
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />
|
||||
<h6 className="text-xl font-semibold mt-6 sm:mt-8 mb-3">{title}</h6>
|
||||
{description && <p className="text-custom-text-300 mb-7 sm:mb-8 px-5">{description}</p>}
|
||||
<div className="flex items-center gap-4">
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ type Props = {
|
|||
text: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
secondaryButton?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -29,7 +28,6 @@ export const NewEmptyState: React.FC<Props> = ({
|
|||
description,
|
||||
image,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
disabled = false,
|
||||
comicBox,
|
||||
}) => {
|
||||
|
|
@ -48,7 +46,7 @@ export const NewEmptyState: React.FC<Props> = ({
|
|||
<h3 className="font-semibold text-2xl">{title}</h3>
|
||||
{description && <p className=" text-lg">{description}</p>}
|
||||
<div className="relative w-full max-w-6xl">
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center items-start relative">
|
||||
|
|
|
|||
|
|
@ -317,9 +317,9 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
|||
</div>
|
||||
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
|
||||
<a className="bg-custom-primary text-white px-4 rounded-md py-2 text-center text-sm font-medium w-full hover:bg-custom-primary/90">
|
||||
<span className="bg-custom-primary text-white px-4 rounded-md py-2 text-center text-sm font-medium w-full hover:bg-custom-primary/90">
|
||||
View Cycle
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||
/>
|
||||
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
|
||||
<a className="flex flex-col justify-between p-4 h-44 w-full min-w-[250px] text-sm rounded bg-custom-background-100 border border-custom-border-100 hover:shadow-md">
|
||||
<span className="flex flex-col justify-between p-4 h-44 w-full min-w-[250px] text-sm rounded bg-custom-background-100 border border-custom-border-100 hover:shadow-md">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-3 truncate">
|
||||
<span className="flex-shrink-0">
|
||||
|
|
@ -268,7 +268,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||
projectId={projectId}
|
||||
/>
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
|
||||
<a className="group flex items-center justify-between gap-5 px-5 py-6 h-16 w-full text-sm bg-custom-background-100 border-b border-custom-border-100 hover:bg-custom-background-90">
|
||||
<span className="group flex items-center justify-between gap-5 px-5 py-6 h-16 w-full text-sm bg-custom-background-100 border-b border-custom-border-100 hover:bg-custom-background-90">
|
||||
<div className="flex items-center gap-3 w-full truncate">
|
||||
<div className="flex items-center gap-4 truncate">
|
||||
<span className="flex-shrink-0">
|
||||
|
|
@ -262,7 +262,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
|
|||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ interface ICycleDelete {
|
|||
export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
||||
const { isOpen, handleClose, cycle, workspaceSlug, projectId } = props;
|
||||
// store
|
||||
const { cycle: cycleStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
const {
|
||||
cycle: cycleStore,
|
||||
trackEvent: { postHogEventTracker },
|
||||
} = useMobxStore();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// states
|
||||
|
|
@ -36,26 +39,23 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
|||
setLoader(true);
|
||||
if (cycle?.id)
|
||||
try {
|
||||
await cycleStore.removeCycle(workspaceSlug, projectId, cycle?.id).then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
await cycleStore
|
||||
.removeCycle(workspaceSlug, projectId, cycle?.id)
|
||||
.then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Cycle deleted successfully.",
|
||||
});
|
||||
postHogEventTracker("CYCLE_DELETE", {
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
postHogEventTracker("CYCLE_DELETE", {
|
||||
state: "FAILED",
|
||||
});
|
||||
});
|
||||
postHogEventTracker(
|
||||
"CYCLE_DELETE",
|
||||
{
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
}).catch((error) => {
|
||||
postHogEventTracker(
|
||||
"CYCLE_DELETE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (cycleId || peekCycle) router.push(`/${workspaceSlug}/projects/${projectId}/cycles`);
|
||||
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ const IntegrationGuide = () => {
|
|||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<Link href={`/${workspaceSlug}/settings/exports?provider=${service.provider}`}>
|
||||
<a>
|
||||
<span>
|
||||
<Button variant="primary" className="capitalize">
|
||||
{service.type}
|
||||
</Button>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
|||
<div className="flex items-center gap-1 p-1 rounded bg-custom-background-80">
|
||||
{GLOBAL_VIEW_LAYOUTS.map((layout) => (
|
||||
<Link key={layout.key} href={`/${workspaceSlug}/${layout.link}`}>
|
||||
<a>
|
||||
<span>
|
||||
<Tooltip tooltipContent={layout.title}>
|
||||
<div
|
||||
className={`w-7 h-[22px] rounded grid place-items-center transition-all hover:bg-custom-background-100 overflow-hidden group ${
|
||||
|
|
@ -133,7 +133,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
|||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||
</FiltersDropdown>
|
||||
{projectId && inboxStore.isInboxEnabled && inboxDetails && (
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxStore.getInboxId(projectId)}`}>
|
||||
<a>
|
||||
<span>
|
||||
<Button variant="neutral-primary" size="sm" className="relative">
|
||||
Inbox
|
||||
{inboxDetails.pending_issue_count > 0 && (
|
||||
|
|
@ -194,7 +194,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import AudioFileIcon from "public/attachment/audio-icon.png";
|
||||
|
||||
export const AudioIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export type AudioIconProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
export const AudioIcon: React.FC<AudioIconProps> = ({ width, height }) => (
|
||||
<Image src={AudioFileIcon} height={height} width={width} alt="AudioFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import CMDIcon from "public/mac-command.svg";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const MacCommandIcon: React.FC<Props> = ({ width = "14", height = "14" }) => (
|
||||
export const MacCommandIcon: React.FC<ImageIconPros> = ({ width = 14, height = 14 }) => (
|
||||
<Image src={CMDIcon} height={height} width={width} alt="CMDIcon" />
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import CssFileIcon from "public/attachment/css-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const CssIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const CssIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={CssFileIcon} height={height} width={width} alt="CssFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import CSVFileIcon from "public/attachment/csv-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const CsvIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const CsvIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={CSVFileIcon} height={height} width={width} alt="CSVFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import DefaultFileIcon from "public/attachment/default-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const DefaultIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const DefaultIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={DefaultFileIcon} height={height} width={width} alt="DefaultFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import DocFileIcon from "public/attachment/doc-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const DocIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const DocIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={DocFileIcon} height={height} width={width} alt="DocFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import FigmaFileIcon from "public/attachment/figma-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const FigmaIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const FigmaIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={FigmaFileIcon} height={height} width={width} alt="FigmaFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import HtmlFileIcon from "public/attachment/html-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const HtmlIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const HtmlIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={HtmlFileIcon} height={height} width={width} alt="HtmlFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import ImgFileIcon from "public/attachment/img-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const ImgIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const ImgIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={ImgFileIcon} height={height} width={width} alt="ImgFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import JpgFileIcon from "public/attachment/jpg-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const JpgIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const JpgIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={JpgFileIcon} height={height} width={width} alt="JpgFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import JsFileIcon from "public/attachment/js-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const JavaScriptIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const JavaScriptIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={JsFileIcon} height={height} width={width} alt="JsFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import PDFFileIcon from "public/attachment/pdf-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const PdfIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const PdfIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={PDFFileIcon} height={height} width={width} alt="PDFFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import PngFileIcon from "public/attachment/png-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const PngIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const PngIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={PngFileIcon} height={height} width={width} alt="PngFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import SheetFileIcon from "public/attachment/excel-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const SheetIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const SheetIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={SheetFileIcon} height={height} width={width} alt="SheetFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import SvgFileIcon from "public/attachment/svg-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const SvgIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const SvgIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={SvgFileIcon} height={height} width={width} alt="SvgFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import TxtFileIcon from "public/attachment/txt-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const TxtIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const TxtIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={TxtFileIcon} height={height} width={width} alt="TxtFileIcon" />
|
||||
);
|
||||
|
|
|
|||
5
web/components/icons/types.d.ts
vendored
5
web/components/icons/types.d.ts
vendored
|
|
@ -4,3 +4,8 @@ export type Props = {
|
|||
height?: string | number;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export type ImageIconPros = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import type { Props } from "./types";
|
||||
// image
|
||||
import VideoFileIcon from "public/attachment/video-icon.png";
|
||||
// type
|
||||
import type { ImageIconPros } from "./types";
|
||||
|
||||
export const VideoIcon: React.FC<Props> = ({ width, height }) => (
|
||||
export const VideoIcon: React.FC<ImageIconPros> = ({ width, height }) => (
|
||||
<Image src={VideoFileIcon} height={height} width={width} alt="VideoFileIcon" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const InboxIssueCard: React.FC<Props> = (props) => {
|
|||
|
||||
return (
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${issue.issue_inbox[0].id}`}>
|
||||
<a>
|
||||
<span>
|
||||
<div
|
||||
id={issue.id}
|
||||
className={`relative min-h-[5rem] cursor-pointer select-none space-y-3 py-2 px-4 border-b border-custom-border-200 hover:bg-custom-primary/5 ${
|
||||
|
|
@ -91,7 +91,7 @@ export const InboxIssueCard: React.FC<Props> = (props) => {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { FC } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// ui
|
||||
import { Button, Input, ToggleSwitch } from "@plane/ui";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IInstance, IInstanceAdmin } from "types/instance";
|
||||
// hooks
|
||||
|
|
|
|||
|
|
@ -97,16 +97,13 @@ export const InstanceHelpSection: FC = () => {
|
|||
{helpOptions.map(({ name, Icon, href, onClick }) => {
|
||||
if (href)
|
||||
return (
|
||||
<Link href={href} key={name}>
|
||||
<a
|
||||
target="_blank"
|
||||
className="flex items-center gap-x-2 rounded px-2 py-1 text-xs hover:bg-custom-background-80"
|
||||
>
|
||||
<Link href={href} key={name} target="_blank">
|
||||
<span className="flex items-center gap-x-2 rounded px-2 py-1 text-xs hover:bg-custom-background-80">
|
||||
<div className="grid place-items-center flex-shrink-0">
|
||||
<Icon className="text-custom-text-200 h-3.5 w-3.5" size={14} />
|
||||
</div>
|
||||
<span className="text-xs">{name}</span>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ export const InstanceAdminRestriction: FC<InstanceAdminRestrictionProps> = ({ re
|
|||
</div>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Link href={`/${redirectWorkspaceSlug}`}>
|
||||
<a>
|
||||
<span>
|
||||
<Button variant="primary" size="sm">
|
||||
<LayoutGrid width={16} height={16} />
|
||||
To the workspace
|
||||
</Button>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ export const InstanceSidebarDropdown = observer(() => {
|
|||
<Tooltip position="bottom-left" tooltipContent="Exit God Mode">
|
||||
<div className="flex-shrink-0">
|
||||
<Link href={`/${redirectWorkspaceSlug}`}>
|
||||
<a>
|
||||
<span>
|
||||
<LogIn className="h-5 w-5 text-custom-text-200 rotate-180" />
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
@ -119,10 +119,10 @@ export const InstanceSidebarDropdown = observer(() => {
|
|||
{PROFILE_LINKS.map((link) => (
|
||||
<Menu.Item key={link.key} as="button" type="button">
|
||||
<Link href={link.link}>
|
||||
<a className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
|
||||
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
|
||||
<link.icon className="h-4 w-4 stroke-[1.5]" />
|
||||
{link.name}
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
))}
|
||||
|
|
@ -142,9 +142,9 @@ export const InstanceSidebarDropdown = observer(() => {
|
|||
<div className="p-2 pb-0">
|
||||
<Menu.Item as="button" type="button" className="w-full">
|
||||
<Link href={`/${redirectWorkspaceSlug}`}>
|
||||
<a className="flex w-full items-center justify-center rounded px-2 py-1 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 bg-custom-primary-100/20 hover:bg-custom-primary-100/30">
|
||||
<span className="flex w-full items-center justify-center rounded px-2 py-1 text-sm font-medium text-custom-primary-100 hover:text-custom-primary-200 bg-custom-primary-100/20 hover:bg-custom-primary-100/30">
|
||||
Exit God Mode
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const InstanceAdminSidebarMenu = () => {
|
|||
|
||||
return (
|
||||
<Link key={index} href={item.href}>
|
||||
<a className="block w-full">
|
||||
<span className="block w-full">
|
||||
<Tooltip tooltipContent={item.name} position="right" className="ml-2" disabled={!sidebarCollapsed}>
|
||||
<div
|
||||
className={`group flex w-full items-center gap-3 rounded-md px-3 py-2 outline-none ${
|
||||
|
|
@ -84,7 +84,7 @@ export const InstanceAdminSidebarMenu = () => {
|
|||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -165,10 +165,10 @@ export const GithubImporterRoot: React.FC<Props> = () => {
|
|||
<form onSubmit={handleSubmit(createGithubImporterService)}>
|
||||
<div className="space-y-2 mt-4">
|
||||
<Link href={`/${workspaceSlug}/settings/imports`}>
|
||||
<div className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
|
||||
<span className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
|
||||
<ArrowLeft className="h-3 w-3" />
|
||||
<div>Cancel import & go back</div>
|
||||
</div>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div className="space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4">
|
||||
|
|
|
|||
|
|
@ -89,9 +89,9 @@ const IntegrationGuide = () => {
|
|||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<Link href={`/${workspaceSlug}/settings/imports?provider=${service.provider}`}>
|
||||
<a>
|
||||
<span>
|
||||
<Button variant="primary">{service.type}</Button>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
|||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
const {
|
||||
|
|
@ -31,10 +35,8 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
|||
<h3 className="font-semibold">Jira Personal Access Token</h3>
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Get to know your access token by navigating to{" "}
|
||||
<Link href="https://id.atlassian.com/manage-profile/security/api-tokens">
|
||||
<a className="text-custom-primary underline" target="_blank" rel="noreferrer">
|
||||
Atlassian Settings
|
||||
</a>
|
||||
<Link href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" rel="noreferrer">
|
||||
<span className="text-custom-primary underline">Atlassian Settings</span>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -192,7 +194,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
|||
type="button"
|
||||
onClick={() => {
|
||||
setTrackElement("JIRA_IMPORT_DETAIL");
|
||||
commandPaletteStore.toggleCreateProjectModal(true)
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
}}
|
||||
className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||
import { mutate } from "swr";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
// icons
|
||||
import { ArrowLeft, Check, List, Settings, Users2 } from "lucide-react";
|
||||
import { ArrowLeft, Check, List, Settings } from "lucide-react";
|
||||
// services
|
||||
import { JiraImporterService } from "services/integrations";
|
||||
// fetch keys
|
||||
|
|
@ -100,12 +100,12 @@ export const JiraImporterRoot: React.FC<Props> = () => {
|
|||
return (
|
||||
<div className="flex h-full flex-col space-y-2 mt-4">
|
||||
<Link href={`/${workspaceSlug}/settings/imports`}>
|
||||
<div className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
|
||||
<span className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
|
||||
<div>
|
||||
<ArrowLeft className="h-3 w-3" />
|
||||
</div>
|
||||
<div>Cancel import & go back</div>
|
||||
</div>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div className="flex h-full flex-col space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4">
|
||||
|
|
|
|||
|
|
@ -105,11 +105,11 @@ export const IssueActivitySection: React.FC<Props> = ({
|
|||
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
|
||||
) : (
|
||||
<Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
|
||||
<a className="text-gray font-medium">
|
||||
<span className="text-gray font-medium">
|
||||
{activityItem.actor_detail.is_bot
|
||||
? activityItem.actor_detail.first_name
|
||||
: activityItem.actor_detail.display_name}
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
)}{" "}
|
||||
{message}{" "}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ export const IssueAttachments = () => {
|
|||
key={file.id}
|
||||
className="flex h-[60px] items-center justify-between gap-1 rounded-md border-[2px] border-custom-border-200 bg-custom-background-100 px-4 py-2 text-sm"
|
||||
>
|
||||
<Link href={file.asset}>
|
||||
<a target="_blank">
|
||||
<Link href={file.asset} target="_blank">
|
||||
<span>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-7 w-7">{getFileIcon(getFileExtension(file.asset))}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
|
@ -85,7 +85,7 @@ export const IssueAttachments = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ChangeEvent, FC, useCallback, useEffect, useState } from "react";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
// hooks
|
||||
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import debounce from "lodash/debounce";
|
||||
// components
|
||||
import { TextArea } from "@plane/ui";
|
||||
import { RichTextEditor } from "@plane/rich-text-editor";
|
||||
|
|
@ -93,7 +93,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||
});
|
||||
}, [issue, reset]);
|
||||
|
||||
const debouncedFormSave = useDebouncedCallback(async () => {
|
||||
const debouncedFormSave = debounce(async () => {
|
||||
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
|
||||
}, 1500);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,8 @@ import { EmptyState } from "components/common";
|
|||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { viewId } = router.query as { viewId: string };
|
||||
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ import { PlusIcon } from "lucide-react";
|
|||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/empty_issues.webp";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
import { FilterOption } from "components/issues";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, TIssueExtraOptions } from "types";
|
||||
// constants
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "components/issue
|
|||
import { IIssueDisplayProperties, IIssue, IState } from "types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import React, { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
// services
|
||||
import { ModuleService } from "services/module.service";
|
||||
import { IssueService } from "services/issue";
|
||||
// components
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { CreateUpdateIssueModal } from "components/issues/modal";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||
handleSubmit,
|
||||
setFocus,
|
||||
register,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { isSubmitting },
|
||||
} = useForm<IIssue>({ defaultValues });
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ListGroupByHeaderRoot } from "./headers/group-by-root";
|
|||
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
|
||||
// types
|
||||
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||
import { IIssueResponse, IGroupedIssues, TUnGroupedIssues, ViewFlags } from "store/issues/types";
|
||||
import { IIssueResponse, IGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EIssueActions } from "../types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { Avatar, AvatarGroup, Tooltip } from "@plane/ui";
|
|||
import { Placement } from "@popperjs/core";
|
||||
|
||||
export interface IIssuePropertyAssignee {
|
||||
view?: "profile" | "workspace" | "project";
|
||||
projectId: string | null;
|
||||
value: string[] | string;
|
||||
onChange: (data: string[]) => void;
|
||||
|
|
@ -26,7 +25,6 @@ export interface IIssuePropertyAssignee {
|
|||
|
||||
export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer((props) => {
|
||||
const {
|
||||
view,
|
||||
projectId,
|
||||
value,
|
||||
onChange,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { Placement } from "@popperjs/core";
|
|||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IIssuePropertyLabels {
|
||||
view?: "profile" | "workspace" | "project";
|
||||
projectId: string | null;
|
||||
value: string[];
|
||||
onChange: (data: string[]) => void;
|
||||
|
|
@ -28,7 +27,6 @@ export interface IIssuePropertyLabels {
|
|||
|
||||
export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((props) => {
|
||||
const {
|
||||
view,
|
||||
projectId,
|
||||
value,
|
||||
onChange,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { Placement } from "@popperjs/core";
|
|||
import { RootStore } from "store/root";
|
||||
|
||||
export interface IIssuePropertyState {
|
||||
view?: "profile" | "workspace" | "project";
|
||||
projectId: string | null;
|
||||
value: any | string | null;
|
||||
onChange: (state: IState) => void;
|
||||
|
|
@ -29,7 +28,6 @@ export interface IIssuePropertyState {
|
|||
|
||||
export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props) => {
|
||||
const {
|
||||
view,
|
||||
projectId,
|
||||
value,
|
||||
onChange,
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ export const IssueActivityCard: FC<IssueActivityCard> = (props) => {
|
|||
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
|
||||
) : (
|
||||
<Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
|
||||
<a className="text-gray font-medium">
|
||||
<span className="text-gray font-medium">
|
||||
{activityItem.actor_detail.is_bot
|
||||
? activityItem.actor_detail.first_name
|
||||
: activityItem.actor_detail.display_name}
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
)}{" "}
|
||||
{message}{" "}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { RichTextEditor } from "@plane/rich-text-editor";
|
|||
import { TextArea } from "@plane/ui";
|
||||
import { IssueReaction } from "./reactions";
|
||||
// hooks
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import debounce from "lodash/debounce";
|
||||
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// types
|
||||
|
|
@ -74,7 +74,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
|||
}
|
||||
}, [issueTitleCurrentValue, localTitleValue]);
|
||||
|
||||
const debouncedFormSave = useDebouncedCallback(async () => {
|
||||
const debouncedFormSave = debounce(async () => {
|
||||
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
|
||||
}, 1500);
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||
|
||||
await issueDraftService
|
||||
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||
)}
|
||||
<DeleteModuleModal data={module} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
|
||||
<Link href={`/${workspaceSlug}/projects/${module.project}/modules/${module.id}`}>
|
||||
<a className="flex flex-col justify-between p-4 h-44 w-full min-w-[250px] text-sm rounded bg-custom-background-100 border border-custom-border-100 hover:shadow-md">
|
||||
<span className="flex flex-col justify-between p-4 h-44 w-full min-w-[250px] text-sm rounded bg-custom-background-100 border border-custom-border-100 hover:shadow-md">
|
||||
<div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Tooltip tooltipContent={module.name} position="top">
|
||||
|
|
@ -249,7 +249,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
|||
)}
|
||||
<DeleteModuleModal data={module} isOpen={deleteModal} onClose={() => setDeleteModal(false)} />
|
||||
<Link href={`/${workspaceSlug}/projects/${module.project}/modules/${module.id}`}>
|
||||
<a className="group flex items-center justify-between gap-5 px-5 py-6 h-16 w-full text-sm bg-custom-background-100 border-b border-custom-border-100 hover:bg-custom-background-90">
|
||||
<span className="group flex items-center justify-between gap-5 px-5 py-6 h-16 w-full text-sm bg-custom-background-100 border-b border-custom-border-100 hover:bg-custom-background-90">
|
||||
<div className="flex items-center gap-3 w-full truncate">
|
||||
<div className="flex items-center gap-4 truncate">
|
||||
<span className="flex-shrink-0">
|
||||
|
|
@ -225,7 +225,7 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
|||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// components
|
||||
import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "components/modules";
|
||||
import { EmptyState } from "components/common";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// assets
|
||||
|
|
|
|||
|
|
@ -32,14 +32,12 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||
const {
|
||||
workspace: workspaceStore,
|
||||
user: { currentUser, updateCurrentUser },
|
||||
trackEvent: { postHogEventTracker }
|
||||
trackEvent: { postHogEventTracker },
|
||||
} = useMobxStore();
|
||||
|
||||
const {
|
||||
data: invitations,
|
||||
mutate: mutateInvitations,
|
||||
isLoading,
|
||||
} = useSWR(USER_WORKSPACE_INVITATIONS, () => workspaceService.userWorkspaceInvitations());
|
||||
const { data: invitations, mutate: mutateInvitations } = useSWR(USER_WORKSPACE_INVITATIONS, () =>
|
||||
workspaceService.userWorkspaceInvitations()
|
||||
);
|
||||
|
||||
const handleInvitation = (workspace_invitation: IWorkspaceMemberInvitation, action: "accepted" | "withdraw") => {
|
||||
if (action === "accepted") {
|
||||
|
|
@ -74,7 +72,8 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||
.catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker("WORKSPACE_USER_INVITE_ACCEPT", { state: "FAILED" });
|
||||
}).finally(() => setIsJoiningWorkspaces(false));
|
||||
})
|
||||
.finally(() => setIsJoiningWorkspaces(false));
|
||||
};
|
||||
|
||||
return invitations && invitations.length > 0 ? (
|
||||
|
|
@ -89,10 +88,11 @@ export const Invitations: React.FC<Props> = (props) => {
|
|||
return (
|
||||
<div
|
||||
key={invitation.id}
|
||||
className={`flex cursor-pointer items-center gap-2 border p-3.5 rounded ${isSelected
|
||||
? "border-custom-primary-100"
|
||||
: "border-onboarding-border-200 hover:bg-onboarding-background-300/30"
|
||||
}`}
|
||||
className={`flex cursor-pointer items-center gap-2 border p-3.5 rounded ${
|
||||
isSelected
|
||||
? "border-custom-primary-100"
|
||||
: "border-onboarding-border-200 hover:bg-onboarding-background-300/30"
|
||||
}`}
|
||||
onClick={() => handleInvitation(invitation, isSelected ? "withdraw" : "accepted")}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount
|
|||
control,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<IWorkspace>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
|
@ -9,9 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||
import { TourRoot } from "components/onboarding";
|
||||
import { UserGreetingsView } from "components/user";
|
||||
import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "components/workspace";
|
||||
import { Button } from "@plane/ui";
|
||||
// images
|
||||
import emptyDashboard from "public/empty-state/dashboard.svg";
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
import emptyProject from "public/empty-state/dashboard_empty_project.webp";
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
|||
<DeletePageModal isOpen={deletePageModal} onClose={() => setDeletePageModal(false)} data={page} />
|
||||
<li>
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/pages/${page.id}`}>
|
||||
<a>
|
||||
<span>
|
||||
<div className="relative rounded p-4 text-custom-text-200 hover:bg-custom-background-80">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex overflow-hidden items-center gap-2">
|
||||
|
|
@ -295,7 +295,7 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
|||
<div className="flex items-center overflow-x-scroll">
|
||||
{tabsList.map((tab) => (
|
||||
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
|
||||
<a
|
||||
<span
|
||||
className={`border-b-2 p-4 text-sm font-medium outline-none whitespace-nowrap ${
|
||||
router.pathname === tab.selected
|
||||
? "border-custom-primary-100 text-custom-primary-100"
|
||||
|
|
@ -58,7 +58,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
|||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const ProfileStats: React.FC<Props> = ({ userProfile }) => {
|
|||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{overviewCards.map((card) => (
|
||||
<Link key={card.route} href={`/${workspaceSlug}/profile/${userId}/${card.route}`}>
|
||||
<a className="flex items-center gap-3 p-4 rounded border border-custom-border-100 whitespace-nowrap">
|
||||
<span className="flex items-center gap-3 p-4 rounded border border-custom-border-100 whitespace-nowrap">
|
||||
<div className="h-11 w-11 bg-custom-background-90 rounded grid place-items-center">
|
||||
<card.icon className="h-5 w-5" />
|
||||
</div>
|
||||
|
|
@ -51,7 +51,7 @@ export const ProfileStats: React.FC<Props> = ({ userProfile }) => {
|
|||
<p className="text-custom-text-400 text-sm">{card.title}</p>
|
||||
<p className="text-xl font-semibold">{card.value}</p>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ export const ProfileSidebar = () => {
|
|||
{user?.id === userId && (
|
||||
<div className="absolute top-3.5 right-3.5 h-5 w-5 bg-white rounded grid place-items-center">
|
||||
<Link href="/profile">
|
||||
<a className="grid place-items-center text-black">
|
||||
<span className="grid place-items-center text-black">
|
||||
<Pencil className="h-3 w-3" />
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,10 @@ import { observer } from "mobx-react-lite";
|
|||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { ProjectCard } from "components/project";
|
||||
import { EmptyState } from "components/project/empty-state";
|
||||
import { Loader } from "@plane/ui";
|
||||
// images
|
||||
import emptyProject from "public/empty-state/empty_project.webp";
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
|
||||
export interface IProjectCardList {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const EmptyState: React.FC<Props> = ({
|
|||
}) => (
|
||||
<div className="flex items-center lg:p-20 md:px-10 px-5 justify-center h-full w-full">
|
||||
<div className="relative h-full w-full max-w-6xl">
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} layout="fill" />
|
||||
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text ?? ""} layout="fill" />
|
||||
</div>
|
||||
<div className="absolute pt-[30vh] md:pt-[35vh] lg:pt-[45vh] text-center flex flex-col items-center w-full">
|
||||
<h6 className="text-xl font-semibold mt-6">{title}</h6>
|
||||
|
|
|
|||
|
|
@ -76,27 +76,27 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||
<div className="flex items-center gap-x-4 gap-y-2">
|
||||
{memberDetails.avatar && memberDetails.avatar !== "" ? (
|
||||
<Link href={`/${workspaceSlug}/profile/${memberDetails.id}`}>
|
||||
<a className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize text-white">
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize text-white">
|
||||
<img
|
||||
src={memberDetails.avatar}
|
||||
alt={memberDetails.display_name || memberDetails.email}
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded"
|
||||
/>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={`/${workspaceSlug}/profile/${memberDetails.id}`}>
|
||||
<a className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize bg-gray-700 text-white">
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize bg-gray-700 text-white">
|
||||
{(memberDetails.display_name ?? memberDetails.email ?? "?")[0]}
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Link href={`/${workspaceSlug}/profile/${memberDetails.id}`}>
|
||||
<a className="text-sm font-medium">
|
||||
<span className="text-sm font-medium">
|
||||
{memberDetails.first_name} {memberDetails.last_name}
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
<div className="flex items-center">
|
||||
<p className="text-xs text-custom-text-300">{memberDetails.display_name}</p>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
|
|||
import { ProjectMemberService } from "services/project";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// helpers
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
// types
|
||||
import { IProjectMember, TUserProjectRole } from "types";
|
||||
// constants
|
||||
|
|
@ -58,7 +56,7 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
const {
|
||||
user: { currentProjectRole },
|
||||
workspaceMember: { workspaceMembers },
|
||||
trackEvent: { postHogEventTracker }
|
||||
trackEvent: { postHogEventTracker },
|
||||
} = useMobxStore();
|
||||
|
||||
const {
|
||||
|
|
@ -94,22 +92,16 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
|||
type: "success",
|
||||
message: "Member added successfully",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'PROJECT_MEMBER_INVITE',
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
postHogEventTracker("PROJECT_MEMBER_INVITE", {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker(
|
||||
'PROJECT_MEMBER_INVITE',
|
||||
{
|
||||
state: "FAILED",
|
||||
}
|
||||
);
|
||||
postHogEventTracker("PROJECT_MEMBER_INVITE", {
|
||||
state: "FAILED",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
reset(defaultValues);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,11 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { project, provided, snapshot, handleCopyText, shortContextMenu = false } = props;
|
||||
// store
|
||||
const { project: projectStore, theme: themeStore, trackEvent: { setTrackElement } } = useMobxStore();
|
||||
const {
|
||||
project: projectStore,
|
||||
theme: themeStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
|
@ -308,7 +312,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||
|
||||
return (
|
||||
<Link key={item.name} href={item.href}>
|
||||
<a className="block w-full">
|
||||
<span className="block w-full">
|
||||
<Tooltip
|
||||
tooltipContent={`${project?.name}: ${item.name}`}
|
||||
position="right"
|
||||
|
|
@ -326,7 +330,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
|||
{!isCollapsed && item.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import type { IState } from "types";
|
|||
import { STATES_LIST } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { GROUP_CHOICES } from "constants/project";
|
||||
import { stat } from "fs";
|
||||
|
||||
type Props = {
|
||||
data: IState | null;
|
||||
|
|
@ -44,7 +43,10 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// store
|
||||
const { projectState: projectStateStore, trackEvent: { postHogEventTracker, setTrackElement } } = useMobxStore();
|
||||
const {
|
||||
projectState: projectStateStore,
|
||||
trackEvent: { postHogEventTracker, setTrackElement },
|
||||
} = useMobxStore();
|
||||
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
|
|
@ -95,13 +97,10 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
title: "Success!",
|
||||
message: "State created successfully.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_CREATE',
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
postHogEventTracker("STATE_CREATE", {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.status === 400)
|
||||
|
|
@ -116,12 +115,9 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: "State could not be created. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_CREATE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
postHogEventTracker("STATE_CREATE", {
|
||||
state: "FAILED",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -133,13 +129,10 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
.then((res) => {
|
||||
mutate(STATES_LIST(projectId.toString()));
|
||||
handleClose();
|
||||
postHogEventTracker(
|
||||
'STATE_UPDATE',
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
}
|
||||
);
|
||||
postHogEventTracker("STATE_UPDATE", {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
|
|
@ -159,12 +152,9 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: "State could not be updated. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_UPDATE',
|
||||
{
|
||||
state: "FAILED",
|
||||
}
|
||||
);
|
||||
postHogEventTracker("STATE_UPDATE", {
|
||||
state: "FAILED",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -187,8 +177,9 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`group inline-flex items-center text-base font-medium focus:outline-none ${open ? "text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
className={`group inline-flex items-center text-base font-medium focus:outline-none ${
|
||||
open ? "text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
{watch("color") && watch("color") !== "" && (
|
||||
<span
|
||||
|
|
@ -292,10 +283,14 @@ export const CreateUpdateStateInline: React.FC<Props> = observer((props) => {
|
|||
<Button variant="neutral-primary" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit" loading={isSubmitting} onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}
|
||||
}>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_SETTINGS_STATE_PAGE");
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? (data ? "Updating..." : "Creating...") : data ? "Update" : "Create"}
|
||||
</Button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store
|
||||
const { projectState: projectStateStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
const {
|
||||
projectState: projectStateStore,
|
||||
trackEvent: { postHogEventTracker },
|
||||
} = useMobxStore();
|
||||
|
||||
// states
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
|
@ -47,13 +50,10 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
|||
|
||||
await projectStateStore
|
||||
.deleteState(workspaceSlug.toString(), data.project, data.id)
|
||||
.then((res) => {
|
||||
postHogEventTracker(
|
||||
'STATE_DELETE',
|
||||
{
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
.then(() => {
|
||||
postHogEventTracker("STATE_DELETE", {
|
||||
state: "SUCCESS",
|
||||
});
|
||||
handleClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
@ -70,12 +70,9 @@ export const DeleteStateModal: React.FC<Props> = observer((props) => {
|
|||
title: "Error!",
|
||||
message: "State could not be deleted. Please try again.",
|
||||
});
|
||||
postHogEventTracker(
|
||||
'STATE_DELETE',
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
)
|
||||
postHogEventTracker("STATE_DELETE", {
|
||||
state: "FAILED",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setIsDeleteLoading(false);
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export const CircularProgress = ({ progress }: { progress: number }) => {
|
||||
const [circumference, setCircumference] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const radius = 40;
|
||||
const calcCircumference = 2 * Math.PI * radius;
|
||||
setCircumference(calcCircumference);
|
||||
}, []);
|
||||
|
||||
const progressAngle = (progress / 100) * 360 >= 360 ? 359.9 : (progress / 100) * 360;
|
||||
const progressX = 50 + Math.cos((progressAngle - 90) * (Math.PI / 180)) * 40;
|
||||
const progressY = 50 + Math.sin((progressAngle - 90) * (Math.PI / 180)) * 40;
|
||||
|
||||
return (
|
||||
<div className="relative h-5 w-5">
|
||||
<svg className="absolute top-0 left-0" viewBox="0 0 100 100">
|
||||
<circle
|
||||
className="stroke-current"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
strokeWidth="12"
|
||||
fill="none"
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
/>
|
||||
<path
|
||||
className="fill-current"
|
||||
d={`M50 10
|
||||
A40 40 0 ${progress > 50 ? 1 : 0} 1 ${progressX} ${progressY}
|
||||
L50 50 Z`}
|
||||
strokeWidth="12"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
// hooks
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
|
||||
type Props = {
|
||||
clickEvent: React.MouseEvent | null;
|
||||
children: React.ReactNode;
|
||||
title?: string | JSX.Element;
|
||||
isOpen: boolean;
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
const ContextMenu = ({ clickEvent, children, title, isOpen, setIsOpen }: Props) => {
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Close the context menu when clicked outside
|
||||
useOutsideClickDetector(contextMenuRef, () => {
|
||||
if (isOpen) setIsOpen(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const hideContextMenu = () => {
|
||||
if (isOpen) setIsOpen(false);
|
||||
};
|
||||
|
||||
const escapeKeyEvent = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") hideContextMenu();
|
||||
};
|
||||
|
||||
window.addEventListener("click", hideContextMenu);
|
||||
window.addEventListener("keydown", escapeKeyEvent);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("click", hideContextMenu);
|
||||
window.removeEventListener("keydown", escapeKeyEvent);
|
||||
};
|
||||
}, [isOpen, setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const contextMenu = contextMenuRef.current;
|
||||
|
||||
if (contextMenu && isOpen) {
|
||||
const contextMenuWidth = contextMenu.clientWidth;
|
||||
const contextMenuHeight = contextMenu.clientHeight;
|
||||
|
||||
const clickX = clickEvent?.pageX || 0;
|
||||
const clickY = clickEvent?.pageY || 0;
|
||||
|
||||
let top = clickY;
|
||||
// check if there's enough space at the bottom, otherwise show at the top
|
||||
if (clickY + contextMenuHeight > window.innerHeight) top = clickY - contextMenuHeight;
|
||||
|
||||
// check if there's enough space on the right, otherwise show on the left
|
||||
let left = clickX;
|
||||
if (clickX + contextMenuWidth > window.innerWidth) left = clickX - contextMenuWidth;
|
||||
|
||||
contextMenu.style.top = `${top}px`;
|
||||
contextMenu.style.left = `${left}px`;
|
||||
}
|
||||
}, [clickEvent, isOpen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed z-50 top-0 left-0 h-full w-full ${
|
||||
isOpen ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
ref={contextMenuRef}
|
||||
className={`fixed z-50 flex min-w-[8rem] flex-col items-stretch gap-1 rounded-md border border-custom-border-200 bg-custom-background-90 p-2 text-xs shadow-lg`}
|
||||
>
|
||||
{title && (
|
||||
<h4 className="border-b border-custom-border-200 px-1 py-1 pb-2 text-[0.8rem] font-medium">{title}</h4>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type MenuItemProps = {
|
||||
children: JSX.Element | string;
|
||||
renderAs?: "button" | "a";
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
Icon?: any;
|
||||
};
|
||||
|
||||
const MenuItem: React.FC<MenuItemProps> = ({ children, renderAs, href = "", onClick, className = "", Icon }) => (
|
||||
<>
|
||||
{renderAs === "a" ? (
|
||||
<Link href={href}>
|
||||
<a
|
||||
className={`${className} flex w-full items-center gap-2 rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80`}
|
||||
>
|
||||
<>
|
||||
{Icon && <Icon />}
|
||||
{children}
|
||||
</>
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={`${className} flex w-full items-center gap-2 rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<>
|
||||
{Icon && <Icon height={12} width={12} />}
|
||||
{children}
|
||||
</>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
ContextMenu.Item = MenuItem;
|
||||
|
||||
export { ContextMenu };
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
// react-poppper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Menu } from "@headlessui/react";
|
||||
// ui
|
||||
import { DropdownProps } from "components/ui";
|
||||
// icons
|
||||
import { ChevronDown, MoreHorizontal } from "lucide-react";
|
||||
|
||||
export type CustomMenuProps = DropdownProps & {
|
||||
children: React.ReactNode;
|
||||
ellipsis?: boolean;
|
||||
noBorder?: boolean;
|
||||
verticalEllipsis?: boolean;
|
||||
menuButtonOnClick?: (...args: any) => void;
|
||||
};
|
||||
|
||||
const CustomMenu = ({
|
||||
buttonClassName = "",
|
||||
customButtonClassName = "",
|
||||
placement,
|
||||
children,
|
||||
className = "",
|
||||
customButton,
|
||||
disabled = false,
|
||||
ellipsis = false,
|
||||
label,
|
||||
maxHeight = "md",
|
||||
noBorder = false,
|
||||
noChevron = false,
|
||||
optionsClassName = "",
|
||||
verticalEllipsis = false,
|
||||
width = "auto",
|
||||
menuButtonOnClick,
|
||||
}: CustomMenuProps) => {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
return (
|
||||
<Menu as="div" className={`relative w-min text-left ${className}`}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{customButton ? (
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
onClick={menuButtonOnClick}
|
||||
className={customButtonClassName}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<>
|
||||
{ellipsis || verticalEllipsis ? (
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
onClick={menuButtonOnClick}
|
||||
disabled={disabled}
|
||||
className={`relative grid place-items-center rounded p-1 text-custom-text-200 hover:text-custom-text-100 outline-none ${
|
||||
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
<MoreHorizontal className={`h-3.5 w-3.5 ${verticalEllipsis ? "rotate-90" : ""}`} />
|
||||
</button>
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 rounded-md px-2.5 py-1 text-xs whitespace-nowrap duration-300 ${
|
||||
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
|
||||
} ${noBorder ? "" : "border border-custom-border-300 shadow-sm focus:outline-none"} ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-custom-text-200"
|
||||
: "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
{label}
|
||||
{!noChevron && <ChevronDown className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
</Menu.Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Menu.Items>
|
||||
<div
|
||||
className={`z-10 overflow-y-scroll whitespace-nowrap rounded-md border border-custom-border-300 p-1 text-xs shadow-custom-shadow-rg focus:outline-none bg-custom-background-90 my-1 ${
|
||||
maxHeight === "lg"
|
||||
? "max-h-60"
|
||||
: maxHeight === "md"
|
||||
? "max-h-48"
|
||||
: maxHeight === "rg"
|
||||
? "max-h-36"
|
||||
: maxHeight === "sm"
|
||||
? "max-h-28"
|
||||
: ""
|
||||
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="py-1">{children}</div>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
type MenuItemProps = {
|
||||
children: React.ReactNode;
|
||||
renderAs?: "button" | "a";
|
||||
href?: string;
|
||||
onClick?: (args?: any) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const MenuItem: React.FC<MenuItemProps> = ({ children, renderAs, href, onClick, className = "" }) => (
|
||||
<Menu.Item as="div">
|
||||
{({ active, close }) =>
|
||||
renderAs === "a" ? (
|
||||
<Link href={href ?? ""}>
|
||||
<a
|
||||
className={`inline-block w-full select-none truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80 ${
|
||||
active ? "bg-custom-background-80" : ""
|
||||
} ${className}`}
|
||||
onClick={close}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={`w-full select-none truncate rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80 ${
|
||||
active ? "bg-custom-background-80" : ""
|
||||
} ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
CustomMenu.MenuItem = MenuItem;
|
||||
|
||||
export { CustomMenu };
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// icons
|
||||
import { Check, ChevronDown, Search } from "lucide-react";
|
||||
// types
|
||||
import { DropdownProps } from "./types";
|
||||
|
||||
export type CustomSearchSelectProps = DropdownProps & {
|
||||
footerOption?: JSX.Element;
|
||||
onChange: any;
|
||||
options:
|
||||
| {
|
||||
value: any;
|
||||
query: string;
|
||||
content: React.ReactNode;
|
||||
}[]
|
||||
| undefined;
|
||||
} & (
|
||||
| { multiple?: false; value: any } // if multiple is false, value can be anything
|
||||
| {
|
||||
multiple?: true;
|
||||
value: any[] | null; // if multiple is true, value should be an array
|
||||
}
|
||||
);
|
||||
|
||||
export const CustomSearchSelect = ({
|
||||
customButtonClassName = "",
|
||||
buttonClassName = "",
|
||||
className = "",
|
||||
customButton,
|
||||
placement,
|
||||
disabled = false,
|
||||
footerOption,
|
||||
input = false,
|
||||
label,
|
||||
maxHeight = "md",
|
||||
multiple = false,
|
||||
noChevron = false,
|
||||
onChange,
|
||||
options,
|
||||
onOpen,
|
||||
optionsClassName = "",
|
||||
value,
|
||||
width = "auto",
|
||||
}: CustomSearchSelectProps) => {
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const filteredOptions =
|
||||
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
|
||||
|
||||
const props: any = {
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
};
|
||||
|
||||
if (multiple) props.multiple = true;
|
||||
|
||||
return (
|
||||
<Combobox as="div" className={`relative flex-shrink-0 text-left ${className}`} {...props}>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open && onOpen) onOpen();
|
||||
|
||||
return (
|
||||
<>
|
||||
{customButton ? (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-custom-text-200"
|
||||
: "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${customButtonClassName}`}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
) : (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
|
||||
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||
} ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-custom-text-200"
|
||||
: "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
{label}
|
||||
{!noChevron && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
)}
|
||||
<Combobox.Options as={React.Fragment}>
|
||||
<div
|
||||
className={`z-10 min-w-[10rem] border border-custom-border-300 p-2 rounded-md bg-custom-background-90 text-xs shadow-custom-shadow-rg focus:outline-none my-1 ${
|
||||
width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width
|
||||
} ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-custom-border-200 bg-custom-background-90 px-2">
|
||||
<Search className="h-3 w-3 text-custom-text-200" />
|
||||
<Combobox.Input
|
||||
className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Type to search..."
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`mt-2 space-y-1 ${
|
||||
maxHeight === "lg"
|
||||
? "max-h-60"
|
||||
: maxHeight === "md"
|
||||
? "max-h-48"
|
||||
: maxHeight === "rg"
|
||||
? "max-h-36"
|
||||
: maxHeight === "sm"
|
||||
? "max-h-28"
|
||||
: ""
|
||||
} overflow-y-scroll`}
|
||||
>
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={({ active, selected }) =>
|
||||
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||
active || selected ? "bg-custom-background-80" : ""
|
||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
{({ active, selected }) => (
|
||||
<>
|
||||
{option.content}
|
||||
{multiple ? (
|
||||
<div
|
||||
className={`flex items-center justify-center rounded border border-custom-border-400 p-0.5 ${
|
||||
active || selected ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<Check className={`h-3 w-3 ${selected ? "opacity-100" : "opacity-0"}`} />
|
||||
</div>
|
||||
) : (
|
||||
<Check className={`h-3 w-3 ${selected ? "opacity-100" : "opacity-0"}`} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : (
|
||||
<span className="flex items-center gap-2 p-1">
|
||||
<p className="text-left text-custom-text-200 ">No matching results</p>
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<p className="text-center text-custom-text-200">Loading...</p>
|
||||
)}
|
||||
</div>
|
||||
{footerOption}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Combobox>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Listbox } from "@headlessui/react";
|
||||
// icons
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
// types
|
||||
import { DropdownProps } from "./types";
|
||||
|
||||
export type CustomSelectProps = DropdownProps & {
|
||||
children: React.ReactNode;
|
||||
value: any;
|
||||
onChange: any;
|
||||
};
|
||||
|
||||
const CustomSelect = ({
|
||||
customButtonClassName = "",
|
||||
buttonClassName = "",
|
||||
placement,
|
||||
children,
|
||||
className = "",
|
||||
customButton,
|
||||
disabled = false,
|
||||
input = false,
|
||||
label,
|
||||
maxHeight = "md",
|
||||
noChevron = false,
|
||||
onChange,
|
||||
optionsClassName = "",
|
||||
value,
|
||||
width = "auto",
|
||||
}: CustomSelectProps) => {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={`relative flex-shrink-0 text-left ${className}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
<>
|
||||
{customButton ? (
|
||||
<Listbox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${customButtonClassName}`}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Listbox.Button>
|
||||
) : (
|
||||
<Listbox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
|
||||
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||
} ${
|
||||
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
{label}
|
||||
{!noChevron && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||
</button>
|
||||
</Listbox.Button>
|
||||
)}
|
||||
</>
|
||||
<Listbox.Options>
|
||||
<div
|
||||
className={`z-10 border border-custom-border-300 overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-custom-shadow-rg focus:outline-none my-1 ${
|
||||
maxHeight === "lg"
|
||||
? "max-h-60"
|
||||
: maxHeight === "md"
|
||||
? "max-h-48"
|
||||
: maxHeight === "rg"
|
||||
? "max-h-36"
|
||||
: maxHeight === "sm"
|
||||
? "max-h-28"
|
||||
: ""
|
||||
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="space-y-1 p-2">{children}</div>
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
);
|
||||
};
|
||||
|
||||
type OptionProps = {
|
||||
children: React.ReactNode;
|
||||
value: any;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Option: React.FC<OptionProps> = ({ children, value, className }) => (
|
||||
<Listbox.Option
|
||||
value={value}
|
||||
className={({ active, selected }) =>
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${active || selected ? "bg-custom-background-80" : ""} ${
|
||||
selected ? "text-custom-text-100" : "text-custom-text-200"
|
||||
} ${className}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">{children}</div>
|
||||
{selected && <Check className="h-4 w-4 flex-shrink-0" />}
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
);
|
||||
|
||||
CustomSelect.Option = Option;
|
||||
|
||||
export { CustomSelect };
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export * from "./context-menu";
|
||||
export * from "./custom-menu";
|
||||
export * from "./custom-search-select";
|
||||
export * from "./custom-select";
|
||||
export * from "./types.d";
|
||||
18
web/components/ui/dropdowns/types.d.ts
vendored
18
web/components/ui/dropdowns/types.d.ts
vendored
|
|
@ -1,18 +0,0 @@
|
|||
import { Placement } from "@popperjs/core";
|
||||
|
||||
export type DropdownProps = {
|
||||
customButtonClassName?: string;
|
||||
buttonClassName?: string;
|
||||
customButtonClassName?: string;
|
||||
className?: string;
|
||||
customButton?: JSX.Element;
|
||||
disabled?: boolean;
|
||||
input?: boolean;
|
||||
label?: string | JSX.Element;
|
||||
maxHeight?: "sm" | "rg" | "md" | "lg";
|
||||
noChevron?: boolean;
|
||||
onOpen?: () => void;
|
||||
optionsClassName?: string;
|
||||
width?: "auto" | string;
|
||||
placement?: Placement;
|
||||
};
|
||||
|
|
@ -30,10 +30,10 @@ const EmptySpace: React.FC<EmptySpaceProps> = ({ title, description, children, I
|
|||
{link ? (
|
||||
<div className="mt-6 flex">
|
||||
<Link href={link.href}>
|
||||
<a className="text-sm font-medium text-custom-primary hover:text-custom-primary">
|
||||
<span className="text-sm font-medium text-custom-primary hover:text-custom-primary">
|
||||
{link.text}
|
||||
<span aria-hidden="true"> →</span>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
iconName: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Icon: React.FC<Props> = ({ iconName, className = "" }) => (
|
||||
<span className={`material-symbols-rounded text-sm leading-5 font-light ${className}`}>{iconName}</span>
|
||||
);
|
||||
|
|
@ -1,23 +1,12 @@
|
|||
export * from "./dropdowns";
|
||||
export * from "./graphs";
|
||||
export * from "./input";
|
||||
export * from "./text-area";
|
||||
export * from "./date";
|
||||
export * from "./datepicker";
|
||||
export * from "./empty-space";
|
||||
export * from "./icon";
|
||||
export * from "./labels-list";
|
||||
export * from "./linear-progress-indicator";
|
||||
export * from "./loader";
|
||||
export * from "./multi-level-dropdown";
|
||||
export * from "./multi-level-select";
|
||||
export * from "./progress-bar";
|
||||
export * from "./spinner";
|
||||
export * from "./tooltip";
|
||||
export * from "./toggle-switch";
|
||||
export * from "./markdown-to-component";
|
||||
export * from "./product-updates-modal";
|
||||
export * from "./integration-and-import-export-banner";
|
||||
export * from "./range-datepicker";
|
||||
export * from "./circular-progress";
|
||||
export * from "./profile-empty-state";
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
// types
|
||||
import { Props } from "./types";
|
||||
|
||||
export const Input: React.FC<Props> = ({
|
||||
label,
|
||||
value,
|
||||
name,
|
||||
register,
|
||||
validations,
|
||||
error,
|
||||
mode = "primary",
|
||||
onChange,
|
||||
className = "",
|
||||
type,
|
||||
id,
|
||||
size = "rg",
|
||||
fullWidth = true,
|
||||
...rest
|
||||
}) => (
|
||||
<>
|
||||
{label && (
|
||||
<label htmlFor={id} className="text-custom-text-200 mb-2">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
type={type}
|
||||
id={id}
|
||||
value={value}
|
||||
{...(register && register(name ?? "", validations))}
|
||||
onChange={(e) => {
|
||||
register && register(name ?? "").onChange(e);
|
||||
onChange && onChange(e);
|
||||
}}
|
||||
className={`block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${
|
||||
mode === "primary"
|
||||
? "rounded-md border border-custom-border-200"
|
||||
: mode === "transparent"
|
||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
|
||||
: mode === "trueTransparent"
|
||||
? "rounded border-none bg-transparent ring-0"
|
||||
: ""
|
||||
} ${error ? "border-red-500" : ""} ${error && mode === "primary" ? "bg-red-500/20" : ""} ${
|
||||
fullWidth ? "w-full" : ""
|
||||
} ${size === "rg" ? "px-3 py-2" : size === "lg" ? "p-3" : ""} ${className}`}
|
||||
{...rest}
|
||||
/>
|
||||
{error?.message && <div className="text-sm text-red-500">{error.message}</div>}
|
||||
</>
|
||||
);
|
||||
15
web/components/ui/input/types.d.ts
vendored
15
web/components/ui/input/types.d.ts
vendored
|
|
@ -1,15 +0,0 @@
|
|||
import * as React from "react";
|
||||
import type { UseFormRegister, RegisterOptions } from "react-hook-form";
|
||||
|
||||
export interface Props extends React.ComponentPropsWithoutRef<"input"> {
|
||||
label?: string;
|
||||
name?: string;
|
||||
value?: string | number | readonly string[];
|
||||
mode?: "primary" | "transparent" | "trueTransparent" | "secondary" | "disabled";
|
||||
register?: UseFormRegister<any>;
|
||||
validations?: RegisterOptions;
|
||||
error?: any;
|
||||
className?: string;
|
||||
size?: "rg" | "lg";
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import React from "react";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
||||
type Props = {
|
||||
data: any;
|
||||
noTooltip?: boolean;
|
||||
};
|
||||
|
||||
export const LinearProgressIndicator: React.FC<Props> = ({ data, noTooltip = false }) => {
|
||||
const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let progress = 0;
|
||||
|
||||
const bars = data.map((item: any) => {
|
||||
const width = `${(item.value / total) * 100}%`;
|
||||
const style = {
|
||||
width,
|
||||
backgroundColor: item.color,
|
||||
};
|
||||
progress += item.value;
|
||||
if (noTooltip) return <div style={style} />;
|
||||
else
|
||||
return (
|
||||
<Tooltip key={item.id} tooltipContent={`${item.name} ${Math.round(item.value)}%`}>
|
||||
<div style={style} />
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex h-1 w-full items-center justify-between gap-1">
|
||||
{total === 0 ? (
|
||||
<div className="flex h-full w-full gap-1 bg-neutral-500">{bars}</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full gap-1">{bars}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Loader = ({ children, className = "" }: Props) => (
|
||||
<div className={`${className} animate-pulse`} role="status">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
type ItemProps = {
|
||||
height?: string;
|
||||
width?: string;
|
||||
};
|
||||
|
||||
const Item: React.FC<ItemProps> = ({ height = "auto", width = "auto" }) => (
|
||||
<div className="rounded-md bg-custom-background-80" style={{ height: height, width: width }} />
|
||||
);
|
||||
|
||||
Loader.Item = Item;
|
||||
|
||||
export { Loader };
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
maxValue?: number;
|
||||
value?: number;
|
||||
radius?: number;
|
||||
strokeWidth?: number;
|
||||
activeStrokeColor?: string;
|
||||
inactiveStrokeColor?: string;
|
||||
};
|
||||
|
||||
export const ProgressBar: React.FC<Props> = ({
|
||||
maxValue = 0,
|
||||
value = 0,
|
||||
radius = 8,
|
||||
strokeWidth = 2,
|
||||
activeStrokeColor = "#3e98c7",
|
||||
inactiveStrokeColor = "#ddd",
|
||||
}) => {
|
||||
// PIE Calc Fn
|
||||
const generatePie = (value: any) => {
|
||||
const x = radius - Math.cos((2 * Math.PI) / (100 / value)) * radius;
|
||||
const y = radius + Math.sin((2 * Math.PI) / (100 / value)) * radius;
|
||||
const long = value <= 50 ? 0 : 1;
|
||||
const d = `M${radius} ${radius} L${radius} ${0} A${radius} ${radius} 0 ${long} 1 ${y} ${x} Z`;
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
// ---- PIE Area Calc --------
|
||||
const calculatePieValue = (numberOfBars: any) => {
|
||||
const angle = 360 / numberOfBars;
|
||||
const pieValue = Math.floor(angle / 4);
|
||||
return pieValue < 1 ? 1 : Math.floor(angle / 4);
|
||||
};
|
||||
|
||||
// ---- PIE Render Fn --------
|
||||
const renderPie = (i: any) => {
|
||||
const DIRECTION = -1;
|
||||
// Rotation Calc
|
||||
const primaryRotationAngle = (maxValue - 1) * (360 / maxValue);
|
||||
const rotationAngle = -1 * DIRECTION * primaryRotationAngle + i * DIRECTION * primaryRotationAngle;
|
||||
const rotationTransformation = `rotate(${rotationAngle}, ${radius}, ${radius})`;
|
||||
const pieValue = calculatePieValue(maxValue);
|
||||
const dValue = generatePie(pieValue);
|
||||
const fillColor = value > 0 && i <= value ? activeStrokeColor : inactiveStrokeColor;
|
||||
|
||||
return (
|
||||
<path
|
||||
style={{ opacity: i === 0 ? 0 : 1 }}
|
||||
key={i}
|
||||
d={dValue}
|
||||
fill={fillColor}
|
||||
transform={rotationTransformation}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// combining the Pies
|
||||
const renderOuterCircle = () => [...Array(maxValue + 1)].map((e, i) => renderPie(i));
|
||||
|
||||
return (
|
||||
<svg width={radius * 2} height={radius * 2}>
|
||||
{renderOuterCircle()}
|
||||
<circle r={radius - strokeWidth} cx={radius} cy={radius} className="progress-bar" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
export const Spinner: React.FC = () => (
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-8 w-8 animate-spin fill-blue-600 text-custom-text-200"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Loading...</span>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
// types
|
||||
import { Props } from "./types";
|
||||
|
||||
// Updates the height of a <textarea> when the value changes.
|
||||
const useAutoSizeTextArea = (textAreaRef: HTMLTextAreaElement | null, value: any) => {
|
||||
useEffect(() => {
|
||||
if (textAreaRef) {
|
||||
// We need to reset the height momentarily to get the correct scrollHeight for the textarea
|
||||
textAreaRef.style.height = "0px";
|
||||
const scrollHeight = textAreaRef.scrollHeight;
|
||||
|
||||
// We then set the height directly, outside of the render loop
|
||||
// Trying to set this with state or a ref will product an incorrect value.
|
||||
textAreaRef.style.height = scrollHeight + "px";
|
||||
}
|
||||
}, [textAreaRef, value]);
|
||||
};
|
||||
|
||||
export const TextArea: React.FC<Props> = ({
|
||||
id,
|
||||
label,
|
||||
className = "",
|
||||
value,
|
||||
placeholder,
|
||||
name,
|
||||
register,
|
||||
mode = "primary",
|
||||
rows,
|
||||
cols,
|
||||
disabled,
|
||||
error,
|
||||
validations,
|
||||
noPadding = false,
|
||||
onChange,
|
||||
...rest
|
||||
}) => {
|
||||
const [textareaValue, setTextareaValue] = useState(value ?? "");
|
||||
|
||||
const textAreaRef = useRef<any>(null);
|
||||
|
||||
useAutoSizeTextArea(textAreaRef.current, textareaValue);
|
||||
|
||||
return (
|
||||
<>
|
||||
{label && (
|
||||
<label htmlFor={id} className="mb-2 text-custom-text-200">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<textarea
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
rows={rows}
|
||||
cols={cols}
|
||||
disabled={disabled}
|
||||
{...(register && register(name, validations))}
|
||||
ref={(e) => {
|
||||
textAreaRef.current = e;
|
||||
if (register) register(name).ref(e);
|
||||
}}
|
||||
onChange={(e) => {
|
||||
register && register(name).onChange(e);
|
||||
onChange && onChange(e);
|
||||
setTextareaValue(e.target.value);
|
||||
}}
|
||||
className={`no-scrollbar w-full bg-transparent placeholder-custom-text-400 ${
|
||||
noPadding ? "" : "px-3 py-2"
|
||||
} outline-none ${
|
||||
mode === "primary"
|
||||
? "rounded-md border border-custom-border-200"
|
||||
: mode === "transparent"
|
||||
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-theme"
|
||||
: ""
|
||||
} ${error ? "border-red-500" : ""} ${error && mode === "primary" ? "bg-red-100" : ""} ${className}`}
|
||||
{...rest}
|
||||
/>
|
||||
{error?.message && <div className="text-sm text-red-500">{error.message}</div>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
13
web/components/ui/text-area/types.d.ts
vendored
13
web/components/ui/text-area/types.d.ts
vendored
|
|
@ -1,13 +0,0 @@
|
|||
import React from "react";
|
||||
import type { UseFormRegister, RegisterOptions, FieldError } from "react-hook-form";
|
||||
|
||||
export interface Props extends React.ComponentPropsWithoutRef<"textarea"> {
|
||||
label?: string;
|
||||
value?: string | number | readonly string[];
|
||||
name: string;
|
||||
register?: UseFormRegister<any>;
|
||||
mode?: "primary" | "transparent" | "secondary" | "disabled";
|
||||
validations?: RegisterOptions;
|
||||
error?: FieldError | Merge<FieldError, FieldErrorsImpl<any>>;
|
||||
noPadding?: boolean;
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import { Switch } from "@headlessui/react";
|
||||
|
||||
type Props = {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const ToggleSwitch: React.FC<Props> = (props) => {
|
||||
const { value, onChange, label, size = "sm", disabled, className } = props;
|
||||
|
||||
return (
|
||||
<Switch
|
||||
checked={value}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
className={`relative flex-shrink-0 inline-flex ${
|
||||
size === "sm" ? "h-4 w-6" : size === "md" ? "h-5 w-8" : "h-6 w-10"
|
||||
} flex-shrink-0 cursor-pointer rounded-full border border-custom-border-200 transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
value ? "bg-custom-primary-100" : "bg-gray-700"
|
||||
} ${className || ""} ${disabled ? "cursor-not-allowed" : ""}`}
|
||||
>
|
||||
<span className="sr-only">{label}</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`self-center inline-block ${
|
||||
size === "sm" ? "h-2 w-2" : size === "md" ? "h-3 w-3" : "h-4 w-4"
|
||||
} transform rounded-full shadow ring-0 transition duration-200 ease-in-out ${
|
||||
value
|
||||
? (size === "sm" ? "translate-x-3" : size === "md" ? "translate-x-4" : "translate-x-5") + " bg-white"
|
||||
: "translate-x-0.5 bg-custom-background-90"
|
||||
} ${disabled ? "cursor-not-allowed" : ""}`}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import React from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||
|
||||
type Props = {
|
||||
tooltipHeading?: string;
|
||||
tooltipContent: string | React.ReactNode;
|
||||
position?:
|
||||
| "top"
|
||||
| "right"
|
||||
| "bottom"
|
||||
| "left"
|
||||
| "auto"
|
||||
| "auto-end"
|
||||
| "auto-start"
|
||||
| "bottom-left"
|
||||
| "bottom-right"
|
||||
| "left-bottom"
|
||||
| "left-top"
|
||||
| "right-bottom"
|
||||
| "right-top"
|
||||
| "top-left"
|
||||
| "top-right";
|
||||
children: JSX.Element;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
openDelay?: number;
|
||||
closeDelay?: number;
|
||||
};
|
||||
|
||||
export const Tooltip: React.FC<Props> = ({
|
||||
tooltipHeading,
|
||||
tooltipContent,
|
||||
position = "top",
|
||||
children,
|
||||
disabled = false,
|
||||
className = "",
|
||||
openDelay = 200,
|
||||
closeDelay,
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<Tooltip2
|
||||
disabled={disabled}
|
||||
hoverOpenDelay={openDelay}
|
||||
hoverCloseDelay={closeDelay}
|
||||
content={
|
||||
<div
|
||||
className={`relative z-50 max-w-xs gap-1 rounded-md p-2 text-xs shadow-md ${
|
||||
theme === "custom" ? "bg-custom-background-100 text-custom-text-200" : "bg-black text-gray-400"
|
||||
} break-words overflow-hidden ${className}`}
|
||||
>
|
||||
{tooltipHeading && (
|
||||
<h5 className={`font-medium ${theme === "custom" ? "text-custom-text-100" : "text-white"}`}>
|
||||
{tooltipHeading}
|
||||
</h5>
|
||||
)}
|
||||
{tooltipContent}
|
||||
</div>
|
||||
}
|
||||
position={position}
|
||||
renderTarget={({ ref: eleReference, ...tooltipProps }) =>
|
||||
React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props })
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -57,7 +57,7 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
|
|||
<DeleteProjectViewModal data={view} isOpen={deleteViewModal} onClose={() => setDeleteViewModal(false)} />
|
||||
<div className="group hover:bg-custom-background-90 border-b border-custom-border-200">
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
|
||||
<a className="flex items-center justify-between relative rounded p-4 w-full">
|
||||
<span className="flex items-center justify-between relative rounded p-4 w-full">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-4 overflow-hidden">
|
||||
<div className="grid place-items-center flex-shrink-0 h-10 w-10 rounded bg-custom-background-90 group-hover:bg-custom-background-100">
|
||||
|
|
@ -128,7 +128,7 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ export const WebhooksListItem: FC<IWebhookListItem> = (props) => {
|
|||
return (
|
||||
<div className="border-b border-custom-border-200">
|
||||
<Link href={`/${workspaceSlug}/settings/webhooks/${webhook?.id}`}>
|
||||
<a className="flex items-center justify-between gap-4 px-3.5 py-[18px]">
|
||||
<span className="flex items-center justify-between gap-4 px-3.5 py-[18px]">
|
||||
<h5 className="text-base font-medium truncate">{webhook.url}</h5>
|
||||
<ToggleSwitch value={webhook.is_active} onChange={handleToggle} />
|
||||
</a>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import { Button, CustomSelect, Input } from "@plane/ui";
|
|||
import { IWorkspace } from "types";
|
||||
// constants
|
||||
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "constants/workspace";
|
||||
// events
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
|
||||
type Props = {
|
||||
onSubmit?: (res: IWorkspace) => Promise<void>;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue