diff --git a/web/components/project/create-project-form.tsx b/web/components/project/create-project-form.tsx new file mode 100644 index 000000000..509cf310c --- /dev/null +++ b/web/components/project/create-project-form.tsx @@ -0,0 +1,392 @@ +import { useState, FC, ChangeEvent } from "react"; +import { observer } from "mobx-react-lite"; +import { useForm, Controller } from "react-hook-form"; +import { Info, X } from "lucide-react"; +// ui +import { + Button, + CustomEmojiIconPicker, + CustomSelect, + EmojiIconPickerTypes, + Input, + setToast, + TextArea, + TOAST_TYPE, + Tooltip, +} from "@plane/ui"; +// components +import { ImagePickerPopover } from "components/core"; +import { MemberDropdown } from "components/dropdowns"; +import { ProjectLogo } from "./project-logo"; +// constants +import { PROJECT_CREATED } from "constants/event-tracker"; +import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project"; +// helpers +import { convertHexEmojiToDecimal, getRandomEmoji } from "helpers/emoji.helper"; +import { cn } from "helpers/common.helper"; +import { projectIdentifierSanitizer } from "helpers/project.helper"; +// hooks +import { useEventTracker, useProject } from "hooks/store"; +// types +import { IProject } from "@plane/types"; + +type Props = { + setToFavorite?: boolean; + workspaceSlug: string; + onClose: () => void; + handleNextStep: (projectId: string) => void; +}; + +const defaultValues: Partial = { + cover_image: PROJECT_UNSPLASH_COVERS[Math.floor(Math.random() * PROJECT_UNSPLASH_COVERS.length)], + description: "", + logo_props: { + in_use: "emoji", + emoji: { + value: getRandomEmoji(), + }, + }, + identifier: "", + name: "", + network: 2, + project_lead: null, +}; + +export const CreateProjectForm: FC = observer((props) => { + const { setToFavorite, workspaceSlug, onClose, handleNextStep } = props; + // store + const { captureProjectEvent } = useEventTracker(); + const { addProjectToFavorites, createProject } = useProject(); + // states + const [isChangeInIdentifierRequired, setIsChangeInIdentifierRequired] = useState(true); + // form info + const { + formState: { errors, isSubmitting }, + handleSubmit, + reset, + control, + watch, + setValue, + } = useForm({ + defaultValues, + reValidateMode: "onChange", + }); + + const handleAddToFavorites = (projectId: string) => { + if (!workspaceSlug) return; + + addProjectToFavorites(workspaceSlug.toString(), projectId).catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Couldn't remove the project from favorites. Please try again.", + }); + }); + }; + + const onSubmit = async (formData: Partial) => { + // Upper case identifier + formData.identifier = formData.identifier?.toUpperCase(); + + return createProject(workspaceSlug.toString(), formData) + .then((res) => { + const newPayload = { + ...res, + state: "SUCCESS", + }; + captureProjectEvent({ + eventName: PROJECT_CREATED, + payload: newPayload, + }); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Project created successfully.", + }); + if (setToFavorite) { + handleAddToFavorites(res.id); + } + handleNextStep(res.id); + }) + .catch((err) => { + Object.keys(err.data).map((key) => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: err.data[key], + }); + captureProjectEvent({ + eventName: PROJECT_CREATED, + payload: { + ...formData, + state: "FAILED", + }, + }); + }); + }); + }; + + const handleNameChange = (onChange: any) => (e: ChangeEvent) => { + if (!isChangeInIdentifierRequired) { + onChange(e); + return; + } + if (e.target.value === "") setValue("identifier", ""); + else setValue("identifier", projectIdentifierSanitizer(e.target.value).substring(0, 5)); + onChange(e); + }; + + const handleIdentifierChange = (onChange: any) => (e: ChangeEvent) => { + const { value } = e.target; + const alphanumericValue = projectIdentifierSanitizer(value); + setIsChangeInIdentifierRequired(false); + onChange(alphanumericValue); + }; + + const handleClose = () => { + onClose(); + setIsChangeInIdentifierRequired(true); + setTimeout(() => { + reset(); + }, 300); + }; + + return ( + <> +
+ {watch("cover_image") && ( + Cover image + )} + +
+ +
+
+ ( + + )} + /> +
+
+ ( + + + + } + onChange={(val: any) => { + let logoValue = {}; + + if (val.type === "emoji") + logoValue = { + value: convertHexEmojiToDecimal(val.value.unified), + url: val.value.imageUrl, + }; + else if (val.type === "icon") logoValue = val.value; + + onChange({ + in_use: val.type, + [val.type]: logoValue, + }); + }} + defaultIconColor={value.in_use === "icon" ? value.icon?.color : undefined} + defaultOpen={value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON} + /> + )} + /> +
+
+
+
+
+
+ ( + + )} + /> + + <>{errors?.name?.message} + +
+
+ + /^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) || + "Only Alphanumeric & Non-latin characters are allowed.", + minLength: { + value: 1, + message: "Project ID must at least be of 1 character", + }, + maxLength: { + value: 5, + message: "Project ID must at most be of 5 characters", + }, + }} + render={({ field: { value, onChange } }) => ( + + )} + /> + + + + {errors?.identifier?.message} +
+
+ ( +