refactor: publish project store and components

This commit is contained in:
Aaryan Khandelwal 2024-06-04 21:00:19 +05:30
parent 3fe9e3515b
commit 761c65830c
3 changed files with 107 additions and 116 deletions

View File

@ -25,6 +25,7 @@ type Props = {
}; };
type FormData = { type FormData = {
anchor: string;
id: string | null; id: string | null;
comments: boolean; comments: boolean;
reactions: boolean; reactions: boolean;
@ -34,6 +35,7 @@ type FormData = {
}; };
const defaultValues: FormData = { const defaultValues: FormData = {
anchor: "",
id: null, id: null,
comments: false, comments: false,
reactions: false, reactions: false,
@ -48,34 +50,27 @@ const viewOptions: {
}[] = [ }[] = [
{ key: "list", label: "List" }, { key: "list", label: "List" },
{ key: "kanban", label: "Kanban" }, { key: "kanban", label: "Kanban" },
// { key: "calendar", label: "Calendar" },
// { key: "gantt", label: "Gantt" },
// { key: "spreadsheet", label: "Spreadsheet" },
]; ];
export const PublishProjectModal: React.FC<Props> = observer((props) => { export const PublishProjectModal: React.FC<Props> = observer((props) => {
const { isOpen, project, onClose } = props; const { isOpen, project, onClose } = props;
// hooks
// const { instance } = useInstance();
// states // states
const [isUnPublishing, setIsUnPublishing] = useState(false); const [isUnPublishing, setIsUnPublishing] = useState(false);
const [isUpdateRequired, setIsUpdateRequired] = useState(false); const [isUpdateRequired, setIsUpdateRequired] = useState(false);
// const plane_deploy_url = instance?.config?.space_base_url || "";
const SPACE_URL = (SPACE_BASE_URL === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store hooks // store hooks
const { const {
projectPublishSettings, fetchPublishSettings,
getProjectSettingsAsync, getPublishSettingsByProjectID,
publishProject, publishProject,
updateProjectSettingsAsync, updatePublishSettings,
unPublishProject, unPublishProject,
fetchSettingsLoader, fetchSettingsLoader,
} = useProjectPublish(); } = useProjectPublish();
// derived values
const projectPublishSettings = getPublishSettingsByProjectID(project.id);
// form info // form info
const { const {
control, control,
@ -97,11 +92,12 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
// prefill form with the saved settings if the project is already published // prefill form with the saved settings if the project is already published
useEffect(() => { useEffect(() => {
if (projectPublishSettings && projectPublishSettings !== "not-initialized") { if (!projectPublishSettings) return;
let userBoards: TProjectPublishViews[] = []; let userBoards: TProjectPublishViews[] = [];
if (projectPublishSettings?.views) { if (projectPublishSettings?.view_props) {
const savedViews = projectPublishSettings?.views; const savedViews = projectPublishSettings?.view_props;
if (!savedViews) return; if (!savedViews) return;
@ -124,17 +120,16 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
}; };
reset({ ...updatedData }); reset({ ...updatedData });
}
}, [reset, projectPublishSettings, isOpen]); }, [reset, projectPublishSettings, isOpen]);
// fetch publish settings // fetch publish settings
useEffect(() => { useEffect(() => {
if (!workspaceSlug || !isOpen) return; if (!workspaceSlug || !isOpen) return;
if (projectPublishSettings === "not-initialized") { if (!projectPublishSettings) {
getProjectSettingsAsync(workspaceSlug.toString(), project.id); fetchPublishSettings(workspaceSlug.toString(), project.id);
} }
}, [isOpen, workspaceSlug, project, projectPublishSettings, getProjectSettingsAsync]); }, [fetchPublishSettings, isOpen, project, projectPublishSettings, workspaceSlug]);
const handlePublishProject = async (payload: IProjectPublishSettings) => { const handlePublishProject = async (payload: IProjectPublishSettings) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -145,7 +140,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
const handleUpdatePublishSettings = async (payload: IProjectPublishSettings) => { const handleUpdatePublishSettings = async (payload: IProjectPublishSettings) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await updateProjectSettingsAsync(workspaceSlug.toString(), project.id, payload.id ?? "", payload) await updatePublishSettings(workspaceSlug.toString(), project.id, payload.id ?? "", payload)
.then((res) => { .then((res) => {
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
@ -172,7 +167,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: "Something went wrong while un-publishing the project.", message: "Something went wrong while unpublishing the project.",
}) })
) )
.finally(() => setIsUnPublishing(false)); .finally(() => setIsUnPublishing(false));
@ -214,7 +209,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
reactions: formData.reactions, reactions: formData.reactions,
votes: formData.votes, votes: formData.votes,
inbox: formData.inbox, inbox: formData.inbox,
views: { view_props: {
list: formData.views.includes("list"), list: formData.views.includes("list"),
kanban: formData.views.includes("kanban"), kanban: formData.views.includes("kanban"),
calendar: formData.views.includes("calendar"), calendar: formData.views.includes("calendar"),
@ -223,13 +218,18 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
}, },
}; };
if (project.is_deployed) await handleUpdatePublishSettings({ id: watch("id") ?? "", ...payload }); if (project.is_deployed)
await handleUpdatePublishSettings({
anchor: watch("anchor") ?? "",
id: watch("id") ?? "",
...payload,
});
else await handlePublishProject(payload); else await handlePublishProject(payload);
}; };
// check if an update is required or not // check if an update is required or not
const checkIfUpdateIsRequired = () => { const checkIfUpdateIsRequired = () => {
if (!projectPublishSettings || projectPublishSettings === "not-initialized") return; if (!projectPublishSettings || !projectPublishSettings) return;
const currentSettings = projectPublishSettings; const currentSettings = projectPublishSettings;
const newSettings = getValues(); const newSettings = getValues();
@ -245,7 +245,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
let viewCheckFlag = 0; let viewCheckFlag = 0;
viewOptions.forEach((option) => { viewOptions.forEach((option) => {
if (currentSettings.views[option.key] !== newSettings.views.includes(option.key)) viewCheckFlag++; if (currentSettings.view_props?.[option.key] !== newSettings.views.includes(option.key)) viewCheckFlag++;
}); });
if (viewCheckFlag !== 0) { if (viewCheckFlag !== 0) {
@ -256,6 +256,8 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
setIsUpdateRequired(false); setIsUpdateRequired(false);
}; };
const SPACE_URL = (SPACE_BASE_URL === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
return ( return (
<Transition.Root show={isOpen} as={Fragment}> <Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}> <Dialog as="div" className="relative z-20" onClose={handleClose}>
@ -293,7 +295,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
onClick={() => handleUnPublishProject(watch("id") ?? "")} onClick={() => handleUnPublishProject(watch("id") ?? "")}
loading={isUnPublishing} loading={isUnPublishing}
> >
{isUnPublishing ? "Un-publishing..." : "Un-publish"} {isUnPublishing ? "Unpublishing" : "Unpublish"}
</Button> </Button>
)} )}
</div> </div>
@ -308,12 +310,12 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
</Loader> </Loader>
) : ( ) : (
<div className="px-6"> <div className="px-6">
{project.is_deployed && ( {project.is_deployed && projectPublishSettings && (
<> <>
<div className="relative flex items-center gap-2 rounded-md border border-custom-border-100 bg-custom-background-80 px-3 py-2"> <div className="relative flex items-center gap-2 rounded-md border border-custom-border-100 bg-custom-background-80 px-3 py-2">
<div className="flex-grow truncate text-sm">{`${SPACE_URL}/issues/`}</div> <div className="flex-grow truncate text-sm">{`${SPACE_URL}/issues/${projectPublishSettings.anchor}`}</div>
<div className="relative flex flex-shrink-0 items-center gap-1"> <div className="relative flex flex-shrink-0 items-center gap-1">
<CopyLinkToClipboard copy_link={`${SPACE_URL}/issues`} /> <CopyLinkToClipboard copy_link={`${SPACE_URL}/issues/${projectPublishSettings.anchor}`} />
</div> </div>
</div> </div>
<div className="mt-3 flex items-center gap-1 text-custom-primary-100"> <div className="mt-3 flex items-center gap-1 text-custom-primary-100">

View File

@ -34,7 +34,7 @@ export class ProjectPublishService extends APIService {
projectID: string, projectID: string,
project_publish_id: string, project_publish_id: string,
data: IProjectPublishSettings data: IProjectPublishSettings
): Promise<any> { ): Promise<IProjectPublishSettings> {
return this.patch( return this.patch(
`/api/workspaces/${workspaceSlug}/projects/${projectID}/project-deploy-boards/${project_publish_id}/`, `/api/workspaces/${workspaceSlug}/projects/${projectID}/project-deploy-boards/${project_publish_id}/`,
data data

View File

@ -1,4 +1,5 @@
import set from "lodash/set"; import set from "lodash/set";
import unset from "lodash/unset";
import { observable, action, makeObservable, runInAction } from "mobx"; import { observable, action, makeObservable, runInAction } from "mobx";
// types // types
import { ProjectPublishService } from "@/services/project"; import { ProjectPublishService } from "@/services/project";
@ -12,12 +13,13 @@ export type TProjectPublishViewsSettings = {
}; };
export interface IProjectPublishSettings { export interface IProjectPublishSettings {
anchor?: string;
id?: string; id?: string;
project?: string; project?: string;
comments: boolean; comments: boolean;
reactions: boolean; reactions: boolean;
votes: boolean; votes: boolean;
views: TProjectPublishViewsSettings; view_props: TProjectPublishViewsSettings;
inbox: string | null; inbox: string | null;
} }
@ -26,31 +28,31 @@ export interface IProjectPublishStore {
generalLoader: boolean; generalLoader: boolean;
fetchSettingsLoader: boolean; fetchSettingsLoader: boolean;
// observables // observables
projectPublishSettings: IProjectPublishSettings | "not-initialized"; publishSettingsMap: Record<string, IProjectPublishSettings>; // projectID => IProjectPublishSettings
// project settings actions // helpers
getProjectSettingsAsync: (workspaceSlug: string, projectId: string) => Promise<IProjectPublishSettings>; getPublishSettingsByProjectID: (projectID: string) => IProjectPublishSettings | undefined;
updateProjectSettingsAsync: ( // actions
fetchPublishSettings: (workspaceSlug: string, projectID: string) => Promise<IProjectPublishSettings>;
updatePublishSettings: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectID: string,
projectPublishId: string, projectPublishId: string,
data: IProjectPublishSettings data: IProjectPublishSettings
) => Promise<void>; ) => Promise<IProjectPublishSettings>;
// project publish actions
publishProject: ( publishProject: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectID: string,
data: IProjectPublishSettings data: IProjectPublishSettings
) => Promise<IProjectPublishSettings>; ) => Promise<IProjectPublishSettings>;
unPublishProject: (workspaceSlug: string, projectId: string, projectPublishId: string) => Promise<void>; unPublishProject: (workspaceSlug: string, projectID: string, projectPublishId: string) => Promise<void>;
} }
export class ProjectPublishStore implements IProjectPublishStore { export class ProjectPublishStore implements IProjectPublishStore {
// states // states
generalLoader: boolean = false; generalLoader: boolean = false;
fetchSettingsLoader: boolean = false; fetchSettingsLoader: boolean = false;
// actions // observables
project_id: string | null = null; publishSettingsMap: Record<string, IProjectPublishSettings> = {};
projectPublishSettings: IProjectPublishSettings | "not-initialized" = "not-initialized";
// root store // root store
projectRootStore: ProjectRootStore; projectRootStore: ProjectRootStore;
// services // services
@ -62,12 +64,10 @@ export class ProjectPublishStore implements IProjectPublishStore {
generalLoader: observable.ref, generalLoader: observable.ref,
fetchSettingsLoader: observable.ref, fetchSettingsLoader: observable.ref,
// observables // observables
project_id: observable.ref, publishSettingsMap: observable,
projectPublishSettings: observable.ref, // actions
// project settings actions fetchPublishSettings: action,
getProjectSettingsAsync: action, updatePublishSettings: action,
updateProjectSettingsAsync: action,
// project publish actions
publishProject: action, publishProject: action,
unPublishProject: action, unPublishProject: action,
}); });
@ -77,29 +77,31 @@ export class ProjectPublishStore implements IProjectPublishStore {
this.projectPublishService = new ProjectPublishService(); this.projectPublishService = new ProjectPublishService();
} }
/**
* @description returns the publish settings of a particular project
* @param {string} projectID
* @returns {IProjectPublishSettings | undefined}
*/
getPublishSettingsByProjectID = (projectID: string): IProjectPublishSettings | undefined =>
this.publishSettingsMap?.[projectID] ?? undefined;
/** /**
* Fetches project publish settings * Fetches project publish settings
* @param workspaceSlug * @param workspaceSlug
* @param projectId * @param projectID
* @returns * @returns
*/ */
getProjectSettingsAsync = async (workspaceSlug: string, projectId: string) => { fetchPublishSettings = async (workspaceSlug: string, projectID: string) => {
try { try {
runInAction(() => { runInAction(() => {
this.fetchSettingsLoader = true; this.fetchSettingsLoader = true;
}); });
const response = await this.projectPublishService.getProjectSettingsAsync(workspaceSlug, projectId); const response = await this.projectPublishService.getProjectSettingsAsync(workspaceSlug, projectID);
if (response) {
runInAction(() => { runInAction(() => {
this.projectPublishSettings = response; set(this.publishSettingsMap, [projectID], response);
this.fetchSettingsLoader = false; this.fetchSettingsLoader = false;
}); });
} else {
runInAction(() => {
this.projectPublishSettings = "not-initialized";
this.fetchSettingsLoader = false;
});
}
return response; return response;
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
@ -112,23 +114,21 @@ export class ProjectPublishStore implements IProjectPublishStore {
/** /**
* Publishes project and updates project publish status in the store * Publishes project and updates project publish status in the store
* @param workspaceSlug * @param workspaceSlug
* @param projectId * @param projectID
* @param data * @param data
* @returns * @returns
*/ */
publishProject = async (workspaceSlug: string, projectId: string, data: IProjectPublishSettings) => { publishProject = async (workspaceSlug: string, projectID: string, data: IProjectPublishSettings) => {
try { try {
runInAction(() => { runInAction(() => {
this.generalLoader = true; this.generalLoader = true;
}); });
const response = await this.projectPublishService.createProjectSettingsAsync(workspaceSlug, projectId, data); const response = await this.projectPublishService.createProjectSettingsAsync(workspaceSlug, projectID, data);
if (response) {
runInAction(() => { runInAction(() => {
this.projectPublishSettings = response; set(this.publishSettingsMap, [projectID], response);
set(this.projectRootStore.project.projectMap, [projectId, "is_deployed"], true); set(this.projectRootStore.project.projectMap, [projectID, "is_deployed"], true);
this.generalLoader = false; this.generalLoader = false;
}); });
}
return response; return response;
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
@ -141,14 +141,14 @@ export class ProjectPublishStore implements IProjectPublishStore {
/** /**
* Updates project publish settings * Updates project publish settings
* @param workspaceSlug * @param workspaceSlug
* @param projectId * @param projectID
* @param projectPublishId * @param projectPublishId
* @param data * @param data
* @returns * @returns
*/ */
updateProjectSettingsAsync = async ( updatePublishSettings = async (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectID: string,
projectPublishId: string, projectPublishId: string,
data: IProjectPublishSettings data: IProjectPublishSettings
) => { ) => {
@ -158,26 +158,15 @@ export class ProjectPublishStore implements IProjectPublishStore {
}); });
const response = await this.projectPublishService.updateProjectSettingsAsync( const response = await this.projectPublishService.updateProjectSettingsAsync(
workspaceSlug, workspaceSlug,
projectId, projectID,
projectPublishId, projectPublishId,
data data
); );
if (response) {
const _projectPublishSettings: IProjectPublishSettings = {
id: response?.id || null,
comments: response?.comments || false,
reactions: response?.reactions || false,
votes: response?.votes || false,
views: { ...response?.views },
inbox: response?.inbox || null,
project: response?.project || null,
};
runInAction(() => { runInAction(() => {
this.projectPublishSettings = _projectPublishSettings; set(this.publishSettingsMap, [projectID], response);
this.generalLoader = false; this.generalLoader = false;
}); });
return response; return response;
}
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
this.generalLoader = false; this.generalLoader = false;
@ -189,23 +178,23 @@ export class ProjectPublishStore implements IProjectPublishStore {
/** /**
* Unpublishes project and updates project publish status in the store * Unpublishes project and updates project publish status in the store
* @param workspaceSlug * @param workspaceSlug
* @param projectId * @param projectID
* @param projectPublishId * @param projectPublishId
* @returns * @returns
*/ */
unPublishProject = async (workspaceSlug: string, projectId: string, projectPublishId: string) => { unPublishProject = async (workspaceSlug: string, projectID: string, projectPublishId: string) => {
try { try {
runInAction(() => { runInAction(() => {
this.generalLoader = true; this.generalLoader = true;
}); });
const response = await this.projectPublishService.deleteProjectSettingsAsync( const response = await this.projectPublishService.deleteProjectSettingsAsync(
workspaceSlug, workspaceSlug,
projectId, projectID,
projectPublishId projectPublishId
); );
runInAction(() => { runInAction(() => {
this.projectPublishSettings = "not-initialized"; unset(this.publishSettingsMap, [projectID]);
set(this.projectRootStore.project.projectMap, [projectId, "is_deployed"], false); set(this.projectRootStore.project.projectMap, [projectID, "is_deployed"], false);
this.generalLoader = false; this.generalLoader = false;
}); });
return response; return response;