snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useState } from "react";
import { observer } from "mobx-react";
import { STATE_GROUPS } from "@plane/constants";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
// components
import { StateForm } from "@/components/project-states";
type TStateCreate = {
groupKey: TStateGroups;
createStateCallback: TStateOperationsCallbacks["createState"];
handleClose: () => void;
};
export const StateCreate = observer(function StateCreate(props: TStateCreate) {
const { groupKey, createStateCallback, handleClose } = props;
// states
const [loader, setLoader] = useState(false);
const onCancel = () => {
setLoader(false);
handleClose();
};
const onSubmit = async (formData: Partial<IState>) => {
if (!groupKey) return { status: "error" };
try {
await createStateCallback({ ...formData, group: groupKey });
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "State created successfully.",
});
handleClose();
return { status: "success" };
} catch (error) {
const errorStatus = error as { status: number; data: { error: string } };
if (errorStatus?.status === 400) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "State with that name already exists. Please try again with another name.",
});
return { status: "already_exists" };
} else {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorStatus.data.error ?? "State could not be created. Please try again.",
});
return { status: "error" };
}
}
};
return (
<StateForm
data={{ name: "", description: "", color: STATE_GROUPS[groupKey].color }}
onSubmit={onSubmit}
onCancel={onCancel}
buttonDisabled={loader}
buttonTitle={loader ? `Creating` : `Create`}
/>
);
});

View File

@@ -0,0 +1,122 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect, useState } from "react";
import { TwitterPicker } from "react-color";
import { Field } from "@makeplane/propel/components/field";
import { Input, InputGroup } from "@makeplane/propel/components/input";
import { TextArea, TextAreaGroup } from "@makeplane/propel/components/text-area";
import { Button } from "@plane/propel/button";
import type { IState } from "@plane/types";
import { Popover } from "@plane/ui";
type TStateForm = {
data: Partial<IState>;
onSubmit: (formData: Partial<IState>) => Promise<{ status: string }>;
onCancel: () => void;
buttonDisabled: boolean;
buttonTitle: string;
};
function PopoverButton({ color }: { color?: string }) {
return (
<div
className="group inline-flex h-5 w-5 items-center rounded-sm text-14 font-medium transition-all focus:outline-none"
style={{
backgroundColor: color ?? "black",
}}
/>
);
}
export function StateForm(props: TStateForm) {
const { data, onSubmit, onCancel, buttonDisabled, buttonTitle } = props;
// states
const [formData, setFromData] = useState<Partial<IState> | undefined>(undefined);
const [errors, setErrors] = useState<Partial<Record<keyof IState, string>> | undefined>(undefined);
useEffect(() => {
if (data && !formData) setFromData(data);
}, [data, formData]);
const handleFormData = <T extends keyof IState>(key: T, value: IState[T]) => {
setFromData((prev) => ({ ...prev, [key]: value }));
setErrors((prev) => ({ ...prev, [key]: "" }));
};
const formSubmit = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
const name = formData?.name || undefined;
if (!formData || !name) {
let currentErrors: Partial<Record<keyof IState, string>> = {};
if (!name) currentErrors = { ...currentErrors, name: "Name is required" };
setErrors(currentErrors);
return;
}
try {
await onSubmit(formData);
} catch (error) {
console.log("error", error);
}
};
return (
<div className="relative flex space-x-2 rounded-sm bg-surface-1 p-3">
{/* color */}
<div className="mt-2 h-full flex-shrink-0">
<Popover button={<PopoverButton color={formData?.color} />} panelClassName="mt-4 -ml-3">
<TwitterPicker color={formData?.color} onChange={(value) => handleFormData("color", value.hex)} />
</Popover>
</div>
<div className="w-full space-y-2">
{/* title */}
<Field name="name" invalid={(errors && Boolean(errors.name)) || false}>
<InputGroup size="2xl">
<Input
size="2xl"
id="name"
type="text"
name="name"
placeholder="Name"
value={formData?.name}
onChange={(e) => handleFormData("name", e.target.value)}
maxLength={100}
autoFocus
/>
</InputGroup>
</Field>
{/* description */}
<Field name="description" invalid={(errors && Boolean(errors.description)) || false}>
<TextAreaGroup resize="none">
<TextArea
size="lg"
surface="field"
autoResize
maxRows={8}
id="description"
name="description"
placeholder="Describe this state for your members."
value={formData?.description ?? ""}
onChange={(e) => handleFormData("description", e.target.value)}
/>
</TextAreaGroup>
</Field>
<div className="flex items-center space-x-2">
<Button onClick={formSubmit} variant="primary" size="lg" disabled={buttonDisabled}>
{buttonTitle}
</Button>
<Button type="button" variant="secondary" size="lg" disabled={buttonDisabled} onClick={onCancel}>
Cancel
</Button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./create";
export * from "./update";
export * from "./form";

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useState } from "react";
import { observer } from "mobx-react";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IState, TStateOperationsCallbacks } from "@plane/types";
// components
import { StateForm } from "@/components/project-states";
type TStateUpdate = {
state: IState;
updateStateCallback: TStateOperationsCallbacks["updateState"];
handleClose: () => void;
};
export const StateUpdate = observer(function StateUpdate(props: TStateUpdate) {
const { state, updateStateCallback, handleClose } = props;
// states
const [loader, setLoader] = useState(false);
const onCancel = () => {
setLoader(false);
handleClose();
};
const onSubmit = async (formData: Partial<IState>) => {
if (!state.id) return { status: "error" };
try {
await updateStateCallback(state.id, formData);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "State updated successfully.",
});
handleClose();
return { status: "success" };
} catch (error) {
const errorStatus = error as { status: number };
if (errorStatus?.status === 400) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Another state exists with the same name. Please try again with another name.",
});
return { status: "already_exists" };
} else {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "State could not be updated. Please try again.",
});
return { status: "error" };
}
}
};
return (
<StateForm
data={state}
onSubmit={onSubmit}
onCancel={onCancel}
buttonDisabled={loader}
buttonTitle={loader ? `Updating` : `Update`}
/>
);
});