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:
Akshita Goyal 2024-10-22 12:09:03 +05:30 committed by GitHub
parent fb49644185
commit 0f25f39404
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 137 additions and 32 deletions

View file

@ -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;
};

View file

@ -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(

View file

@ -5,6 +5,7 @@ export type TPopoverButtonDefaultOptions = {
// button and button styling
button?: ReactNode;
buttonClassName?: string;
buttonRefClassName?: string;
disabled?: boolean;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View file

@ -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 (

View file

@ -0,0 +1 @@
export * from "./header";

View file

@ -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 = {

View file

@ -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>
);
})}

View file

@ -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;
});
}
}

View file

@ -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

View file

@ -0,0 +1 @@
export * from "ce/components/projects/settings/intake";