WEB-2381 Chore: intake refactor (#5752)
* chore: intake emails and forms * fix: moved files to ee * fix: intake form ui * fix: settings apis integrated * fix: removed publish api * fix: removed space app * fix: lint issue * fix: removed logs * fix: removed comment * fix: improved success image
This commit is contained in:
parent
fb49644185
commit
0f25f39404
13 changed files with 137 additions and 32 deletions
13
packages/types/src/inbox.d.ts
vendored
13
packages/types/src/inbox.d.ts
vendored
|
|
@ -96,3 +96,16 @@ export type TInboxIssuePaginationInfo = TPaginationInfo & {
|
|||
export type TInboxIssueWithPagination = TInboxIssuePaginationInfo & {
|
||||
results: TInboxIssue[];
|
||||
};
|
||||
|
||||
export type TInboxForm = {
|
||||
anchor: string;
|
||||
id: string;
|
||||
is_disabled: boolean;
|
||||
};
|
||||
|
||||
export type TInboxIssueForm = {
|
||||
name: string;
|
||||
description: string;
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const Popover = (props: TPopover) => {
|
|||
panelClassName = "",
|
||||
children,
|
||||
popoverButtonRef,
|
||||
buttonRefClassName = "",
|
||||
} = props;
|
||||
// states
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
|
||||
|
|
@ -38,7 +39,7 @@ export const Popover = (props: TPopover) => {
|
|||
|
||||
return (
|
||||
<HeadlessReactPopover className={cn("relative flex h-full w-full items-center justify-center", popoverClassName)}>
|
||||
<div ref={setReferenceElement} className="w-full">
|
||||
<div ref={setReferenceElement} className={cn("w-full", buttonRefClassName)}>
|
||||
<HeadlessReactPopover.Button
|
||||
ref={popoverButtonRef as Ref<HTMLButtonElement>}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export type TPopoverButtonDefaultOptions = {
|
|||
// button and button styling
|
||||
button?: ReactNode;
|
||||
buttonClassName?: string;
|
||||
buttonRefClassName?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
|
|
|
|||
BIN
space/public/instance/intake-sent-dark.png
Normal file
BIN
space/public/instance/intake-sent-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
BIN
space/public/instance/intake-sent-light.png
Normal file
BIN
space/public/instance/intake-sent-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// components
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { ProjectInboxHeader } from "./header";
|
||||
import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake";
|
||||
|
||||
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
|
|
|
|||
1
web/ce/components/projects/settings/intake/index.ts
Normal file
1
web/ce/components/projects/settings/intake/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./header";
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
import { ReactNode } from "react";
|
||||
import { FileText, Layers, Timer } from "lucide-react";
|
||||
import { IProject } from "@plane/types";
|
||||
import { ContrastIcon, DiceIcon, Intake } from "@plane/ui";
|
||||
|
||||
export type TProperties = {
|
||||
property: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: ReactNode;
|
||||
isPro: boolean;
|
||||
isEnabled: boolean;
|
||||
renderChildren?: (
|
||||
currentProjectDetails: IProject,
|
||||
isAdmin: boolean,
|
||||
handleSubmit: (featureKey: string, featureProperty: string) => Promise<void>
|
||||
) => ReactNode;
|
||||
};
|
||||
export type TFeatureList = {
|
||||
[key: string]: {
|
||||
property: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: ReactNode;
|
||||
isPro: boolean;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
[key: string]: TProperties;
|
||||
};
|
||||
|
||||
export type TProjectFeatures = {
|
||||
|
|
|
|||
|
|
@ -70,31 +70,40 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
|||
return (
|
||||
<div
|
||||
key={featureItemKey}
|
||||
className="flex items-center justify-between gap-x-8 gap-y-2 border-b border-custom-border-100 bg-custom-background-100 pb-2 pt-4 last:border-b-0"
|
||||
className="gap-x-8 gap-y-2 border-b border-custom-border-100 bg-custom-background-100 pb-2 pt-4"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center rounded bg-custom-background-90 p-3">
|
||||
{featureItem.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-sm font-medium leading-5">{featureItem.title}</h4>
|
||||
{featureItem.isPro && (
|
||||
<Tooltip tooltipContent="Pro feature" position="top">
|
||||
<UpgradeBadge />
|
||||
</Tooltip>
|
||||
)}
|
||||
<div key={featureItemKey} className="flex items-center justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-center justify-center rounded bg-custom-background-90 p-3">
|
||||
{featureItem.icon}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-sm font-medium leading-5">{featureItem.title}</h4>
|
||||
{featureItem.isPro && (
|
||||
<Tooltip tooltipContent="Pro feature" position="top">
|
||||
<UpgradeBadge />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm leading-5 tracking-tight text-custom-text-300">
|
||||
{featureItem.description}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm leading-5 tracking-tight text-custom-text-300">{featureItem.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToggleSwitch
|
||||
value={Boolean(currentProjectDetails?.[featureItem.property as keyof IProject])}
|
||||
onChange={() => handleSubmit(featureItemKey, featureItem.property)}
|
||||
disabled={!featureItem.isEnabled || !isAdmin}
|
||||
size="sm"
|
||||
/>
|
||||
<ToggleSwitch
|
||||
value={Boolean(currentProjectDetails?.[featureItem.property as keyof IProject])}
|
||||
onChange={() => handleSubmit(featureItemKey, featureItem.property)}
|
||||
disabled={!featureItem.isEnabled || !isAdmin}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-14">
|
||||
{currentProjectDetails?.[featureItem.property as keyof IProject] &&
|
||||
featureItem.renderChildren &&
|
||||
featureItem.renderChildren(currentProjectDetails, isAdmin, handleSubmit)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// types
|
||||
import type { TInboxIssue, TIssue, TInboxIssueWithPagination } from "@plane/types";
|
||||
import type { TInboxIssue, TIssue, TInboxIssueWithPagination, TInboxForm } from "@plane/types";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
import { APIService } from "@/services/api.service";
|
||||
// helpers
|
||||
|
|
@ -75,4 +75,30 @@ export class InboxIssueService extends APIService {
|
|||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async retrievePublishForm(workspaceSlug: string, projectId: string): Promise<TInboxForm> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/publish-intake/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updatePublishForm(workspaceSlug: string, projectId: string, is_disabled: boolean): Promise<TInboxIssue> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/publish-intake/`, {
|
||||
is_disabled,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async regeneratePublishForm(workspaceSlug: string, projectId: string): Promise<TInboxIssue> {
|
||||
return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/publish-intake-regenerate/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
TInboxIssueSorting,
|
||||
TInboxIssuePaginationInfo,
|
||||
TInboxIssueSortingOrderByQueryParam,
|
||||
TInboxForm,
|
||||
} from "@plane/types";
|
||||
// helpers
|
||||
import { EInboxIssueCurrentTab, EInboxIssueStatus, EPastDurationFilters, getCustomDates } from "@/helpers/inbox.helper";
|
||||
|
|
@ -39,6 +40,7 @@ export interface IProjectInboxStore {
|
|||
inboxIssuePaginationInfo: TInboxIssuePaginationInfo | undefined;
|
||||
inboxIssues: Record<string, IInboxIssueStore>; // issue_id -> IInboxIssueStore
|
||||
inboxIssueIds: string[];
|
||||
intakeForms: Record<string, TInboxForm>;
|
||||
// computed
|
||||
inboxFilters: Partial<TInboxIssueFilter>; // computed project inbox filters
|
||||
inboxSorting: Partial<TInboxIssueSorting>; // computed project inbox sorting
|
||||
|
|
@ -68,6 +70,9 @@ export interface IProjectInboxStore {
|
|||
) => Promise<void>;
|
||||
fetchInboxPaginationIssues: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
fetchInboxIssueById: (workspaceSlug: string, projectId: string, inboxIssueId: string) => Promise<TInboxIssue>;
|
||||
fetchIntakeForms: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
toggleIntakeForms: (workspaceSlug: string, projectId: string, isDisabled: boolean) => Promise<void>;
|
||||
regenerateIntakeForms: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
createInboxIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
|
|
@ -89,6 +94,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||
inboxIssuePaginationInfo: TInboxIssuePaginationInfo | undefined = undefined;
|
||||
inboxIssues: Record<string, IInboxIssueStore> = {};
|
||||
inboxIssueIds: string[] = [];
|
||||
intakeForms: Record<string, TInboxForm> = {};
|
||||
// services
|
||||
inboxIssueService;
|
||||
|
||||
|
|
@ -103,6 +109,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||
inboxIssuePaginationInfo: observable,
|
||||
inboxIssues: observable,
|
||||
inboxIssueIds: observable,
|
||||
intakeForms: observable,
|
||||
// computed
|
||||
inboxFilters: computed,
|
||||
inboxSorting: computed,
|
||||
|
|
@ -310,6 +317,45 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||
}
|
||||
};
|
||||
|
||||
fetchIntakeForms = async (workspaceSlug: string, projectId: string) => {
|
||||
try {
|
||||
const intakeForms = await this.inboxIssueService.retrievePublishForm(workspaceSlug, projectId);
|
||||
if (intakeForms)
|
||||
runInAction(() => {
|
||||
set(this.intakeForms, projectId, intakeForms);
|
||||
});
|
||||
} catch {
|
||||
console.error("Error fetching the publish forms");
|
||||
}
|
||||
};
|
||||
|
||||
toggleIntakeForms = async (workspaceSlug: string, projectId: string, isDisabled: boolean) => {
|
||||
try {
|
||||
runInAction(() => {
|
||||
set(this.intakeForms, projectId, { ...this.intakeForms[projectId], is_disabled: isDisabled });
|
||||
});
|
||||
await this.inboxIssueService.updatePublishForm(workspaceSlug, projectId, isDisabled);
|
||||
} catch {
|
||||
console.error("Error fetching the publish forms");
|
||||
runInAction(() => {
|
||||
set(this.intakeForms, projectId, { ...this.intakeForms[projectId], is_disabled: !isDisabled });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
regenerateIntakeForms = async (workspaceSlug: string, projectId: string) => {
|
||||
try {
|
||||
const form = await this.inboxIssueService.regeneratePublishForm(workspaceSlug, projectId);
|
||||
if (form) {
|
||||
runInAction(() => {
|
||||
set(this.intakeForms, projectId, form);
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
console.error("Error fetching the publish forms");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description fetch intake issues with paginated data
|
||||
* @param workspaceSlug
|
||||
|
|
|
|||
1
web/ee/components/projects/settings/intake/index.ts
Normal file
1
web/ee/components/projects/settings/intake/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/components/projects/settings/intake";
|
||||
Loading…
Add table
Add a link
Reference in a new issue