-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[WEB-1986] chore: seperated project components for CE #5324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b094197
836c3be
0a6d189
3c05746
888b95c
60bdab0
a3bd1ae
fcc5932
9ac1d17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| "use client"; | ||
|
|
||
| import { ReactNode } from "react"; | ||
| // components | ||
| import { AppHeader, ContentWrapper } from "@/components/core"; | ||
| // local components | ||
| import { ProjectsListHeader } from "@/plane-web/components/projects/header"; | ||
| import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; | ||
| export default function ProjectListLayout({ children }: { children: ReactNode }) { | ||
| return ( | ||
| <> | ||
| <AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} /> | ||
| <ContentWrapper>{children}</ContentWrapper> | ||
| </> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import ProjectPageRoot from "@/plane-web/components/projects/page"; | ||
|
|
||
| const ProjectsPage = () => <ProjectPageRoot />; | ||
| export default ProjectsPage; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,84 +1,4 @@ | ||
| "use client"; | ||
|
|
||
| import { useCallback } from "react"; | ||
| import { observer } from "mobx-react"; | ||
| import { useParams } from "next/navigation"; | ||
| // types | ||
| import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types"; | ||
| // components | ||
| import { PageHead } from "@/components/core"; | ||
| import { ProjectAppliedFiltersList, ProjectCardList } from "@/components/project"; | ||
| // helpers | ||
| import { calculateTotalFilters } from "@/helpers/filter.helper"; | ||
| // hooks | ||
| import { useProject, useProjectFilter, useWorkspace } from "@/hooks/store"; | ||
|
|
||
| const ProjectsPage = observer(() => { | ||
| // store | ||
| const { workspaceSlug } = useParams(); | ||
| const { currentWorkspace } = useWorkspace(); | ||
| const { totalProjectIds, filteredProjectIds } = useProject(); | ||
| const { | ||
| currentWorkspaceFilters, | ||
| currentWorkspaceAppliedDisplayFilters, | ||
| clearAllFilters, | ||
| clearAllAppliedDisplayFilters, | ||
| updateFilters, | ||
| updateDisplayFilters, | ||
| } = useProjectFilter(); | ||
| // derived values | ||
| const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined; | ||
|
|
||
| const handleRemoveFilter = useCallback( | ||
| (key: keyof TProjectFilters, value: string | null) => { | ||
| if (!workspaceSlug) return; | ||
| let newValues = currentWorkspaceFilters?.[key] ?? []; | ||
|
|
||
| if (!value) newValues = []; | ||
| else newValues = newValues.filter((val) => val !== value); | ||
|
|
||
| updateFilters(workspaceSlug.toString(), { [key]: newValues }); | ||
| }, | ||
| [currentWorkspaceFilters, updateFilters, workspaceSlug] | ||
| ); | ||
|
|
||
| const handleRemoveDisplayFilter = useCallback( | ||
| (key: TProjectAppliedDisplayFilterKeys) => { | ||
| if (!workspaceSlug) return; | ||
| updateDisplayFilters(workspaceSlug.toString(), { [key]: false }); | ||
| }, | ||
| [updateDisplayFilters, workspaceSlug] | ||
| ); | ||
|
|
||
| const handleClearAllFilters = useCallback(() => { | ||
| if (!workspaceSlug) return; | ||
| clearAllFilters(workspaceSlug.toString()); | ||
| clearAllAppliedDisplayFilters(workspaceSlug.toString()); | ||
| }, [clearAllFilters, clearAllAppliedDisplayFilters, workspaceSlug]); | ||
|
|
||
| return ( | ||
| <> | ||
| <PageHead title={pageTitle} /> | ||
| <div className="flex h-full w-full flex-col"> | ||
| {(calculateTotalFilters(currentWorkspaceFilters ?? {}) !== 0 || | ||
| currentWorkspaceAppliedDisplayFilters?.length !== 0) && ( | ||
| <div className="border-b border-custom-border-200 px-5 py-3"> | ||
| <ProjectAppliedFiltersList | ||
| appliedFilters={currentWorkspaceFilters ?? {}} | ||
| appliedDisplayFilters={currentWorkspaceAppliedDisplayFilters ?? []} | ||
| handleClearAllFilters={handleClearAllFilters} | ||
| handleRemoveFilter={handleRemoveFilter} | ||
| handleRemoveDisplayFilter={handleRemoveDisplayFilter} | ||
| filteredProjects={filteredProjectIds?.length ?? 0} | ||
| totalProjects={totalProjectIds?.length ?? 0} | ||
| alwaysAllowEditing | ||
| /> | ||
| </div> | ||
| )} | ||
| <ProjectCardList /> | ||
| </div> | ||
| </> | ||
| ); | ||
| }); | ||
| import ProjectPageRoot from "@/plane-web/components/projects/page"; | ||
|
|
||
| const ProjectsPage = () => <ProjectPageRoot />; | ||
| export default ProjectsPage; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { Controller, useFormContext } from "react-hook-form"; | ||
| import { IProject } from "@plane/types"; | ||
| import { CustomSelect } from "@plane/ui"; | ||
| import { MemberDropdown } from "@/components/dropdowns"; | ||
| import { NETWORK_CHOICES } from "@/constants/project"; | ||
|
|
||
| const ProjectAttributes = () => { | ||
| const { control } = useFormContext<IProject>(); | ||
| return ( | ||
| <div className="flex flex-wrap items-center gap-2"> | ||
| <Controller | ||
| name="network" | ||
| control={control} | ||
| render={({ field: { onChange, value } }) => { | ||
| const currentNetwork = NETWORK_CHOICES.find((n) => n.key === value); | ||
|
|
||
| return ( | ||
| <div className="flex-shrink-0 h-7" tabIndex={4}> | ||
| <CustomSelect | ||
| value={value} | ||
| onChange={onChange} | ||
| label={ | ||
| <div className="flex items-center gap-1 h-full"> | ||
| {currentNetwork ? ( | ||
| <> | ||
| <currentNetwork.icon className="h-3 w-3" /> | ||
| {currentNetwork.label} | ||
| </> | ||
| ) : ( | ||
| <span className="text-custom-text-400">Select network</span> | ||
| )} | ||
| </div> | ||
| } | ||
| placement="bottom-start" | ||
| className="h-full" | ||
| buttonClassName="h-full" | ||
| noChevron | ||
| tabIndex={4} | ||
| > | ||
| {NETWORK_CHOICES.map((network) => ( | ||
| <CustomSelect.Option key={network.key} value={network.key}> | ||
| <div className="flex items-start gap-2"> | ||
| <network.icon className="h-3.5 w-3.5" /> | ||
| <div className="-mt-1"> | ||
| <p>{network.label}</p> | ||
| <p className="text-xs text-custom-text-400">{network.description}</p> | ||
| </div> | ||
| </div> | ||
| </CustomSelect.Option> | ||
| ))} | ||
| </CustomSelect> | ||
| </div> | ||
| ); | ||
| }} | ||
| /> | ||
| <Controller | ||
| name="project_lead" | ||
| control={control} | ||
| render={({ field: { value, onChange } }) => { | ||
| if (value === undefined || value === null || typeof value === "string") | ||
| return ( | ||
| <div className="flex-shrink-0 h-7" tabIndex={5}> | ||
| <MemberDropdown | ||
| value={value} | ||
| onChange={(lead) => onChange(lead === value ? null : lead)} | ||
| placeholder="Lead" | ||
| multiple={false} | ||
| buttonVariant="border-with-text" | ||
| tabIndex={5} | ||
| /> | ||
| </div> | ||
| ); | ||
| else return <></>; | ||
| }} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ProjectAttributes; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,139 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState, FC } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { observer } from "mobx-react"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { FormProvider, useForm } from "react-hook-form"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // ui | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { setToast, TOAST_TYPE } from "@plane/ui"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // constants | ||||||||||||||||||||||||||||||||||||||||||||||||
| import ProjectCommonAttributes from "@/components/project/create/common-attributes"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import ProjectCreateHeader from "@/components/project/create/header"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import ProjectCreateButtons from "@/components/project/create/project-create-buttons"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { PROJECT_CREATED } from "@/constants/event-tracker"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { PROJECT_UNSPLASH_COVERS } from "@/constants/project"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // helpers | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { getRandomEmoji } from "@/helpers/emoji.helper"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // hooks | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEventTracker, useProject } from "@/hooks/store"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { usePlatformOS } from "@/hooks/use-platform-os"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { TProject } from "@/plane-web/types/projects"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import ProjectAttributes from "./attributes"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| type Props = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| setToFavorite?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceSlug: string; | ||||||||||||||||||||||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||
| handleNextStep: (projectId: string) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||
| data?: Partial<TProject>; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const defaultValues: Partial<TProject> = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| 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<Props> = 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 methods = useForm<TProject>({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| defaultValues, | ||||||||||||||||||||||||||||||||||||||||||||||||
| reValidateMode: "onChange", | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { handleSubmit, reset, setValue } = methods; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { isMobile } = usePlatformOS(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| 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.", | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improve error handling in Consider adding more specific error handling and logging to help with debugging. - addProjectToFavorites(workspaceSlug.toString(), projectId).catch(() => {
- setToast({
- type: TOAST_TYPE.ERROR,
- title: "Error!",
- message: "Couldn't remove the project from favorites. Please try again.",
- });
- });
+ addProjectToFavorites(workspaceSlug.toString(), projectId).catch((error) => {
+ console.error("Failed to add project to favorites:", error);
+ setToast({
+ type: TOAST_TYPE.ERROR,
+ title: "Error!",
+ message: "Couldn't remove the project from favorites. Please try again.",
+ });
+ });Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const onSubmit = async (formData: Partial<TProject>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // 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 handleClose = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| onClose(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| setIsChangeInIdentifierRequired(true); | ||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| reset(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, 300); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| <FormProvider {...methods}> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <ProjectCreateHeader handleClose={handleClose} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| <form onSubmit={handleSubmit(onSubmit)} className="px-3"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mt-9 space-y-6 pb-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <ProjectCommonAttributes | ||||||||||||||||||||||||||||||||||||||||||||||||
| setValue={setValue} | ||||||||||||||||||||||||||||||||||||||||||||||||
| isMobile={isMobile} | ||||||||||||||||||||||||||||||||||||||||||||||||
| isChangeInIdentifierRequired={isChangeInIdentifierRequired} | ||||||||||||||||||||||||||||||||||||||||||||||||
| setIsChangeInIdentifierRequired={setIsChangeInIdentifierRequired} | ||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <ProjectAttributes /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <ProjectCreateButtons handleClose={handleClose} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </FormProvider> | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| "use client"; | ||
|
|
||
| import { ProjectsBaseHeader } from "@/components/project/header"; | ||
|
|
||
| export const ProjectsListHeader = () => <ProjectsBaseHeader />; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import Root from "@/components/project/root"; | ||
|
|
||
| const ProjectPageRoot = () => <Root />; | ||
|
|
||
| export default ProjectPageRoot; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./projects"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { IProject } from "@plane/types"; | ||
|
|
||
| export type TProject = IProject; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider initializing state with a function.
To avoid potential performance issues, consider initializing the state with a function.
Committable suggestion