From 9215d99e2b87b044d679685cb9a12ce11d2464ab Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:38:02 +0530 Subject: [PATCH] [WEB-4605] feat: initiative logo and icon implementation (#4075) --- apps/web/core/components/project/form.tsx | 1 + .../initiatives/components/form.tsx | 55 +++++++++++++++++-- .../components/initiative-block.tsx | 11 +++- .../details/main/info-section-root.tsx | 49 ++++++++++++++++- .../ee/components/initiatives/header/root.tsx | 14 ++++- apps/web/ee/types/initiative/initiative.d.ts | 2 + packages/constants/src/event-tracker/core.ts | 3 + 7 files changed, 122 insertions(+), 13 deletions(-) diff --git a/apps/web/core/components/project/form.tsx b/apps/web/core/components/project/form.tsx index b5549a7dab..c4d1afe2b0 100644 --- a/apps/web/core/components/project/form.tsx +++ b/apps/web/core/components/project/form.tsx @@ -8,6 +8,7 @@ import { useTranslation } from "@plane/i18n"; // plane imports import { EmojiPicker } from "@plane/propel/emoji-icon-picker"; import { Tooltip } from "@plane/propel/tooltip"; +import { EmojiPicker } from "@plane/propel/emoji-icon-picker"; import { IProject, IWorkspace } from "@plane/types"; import { Button, CustomSelect, Input, TextArea, TOAST_TYPE, setToast, EmojiIconPickerTypes } from "@plane/ui"; import { renderFormattedDate, getFileURL } from "@plane/utils"; diff --git a/apps/web/ee/components/initiatives/components/form.tsx b/apps/web/ee/components/initiatives/components/form.tsx index 1bfadb873b..8e191444a1 100644 --- a/apps/web/ee/components/initiatives/components/form.tsx +++ b/apps/web/ee/components/initiatives/components/form.tsx @@ -2,17 +2,19 @@ import { FC, useState } from "react"; import { useParams } from "next/navigation"; // plane imports import { useTranslation } from "@plane/i18n"; +import { EmojiPicker } from "@plane/propel/emoji-icon-picker"; import { EFileAssetType } from "@plane/types"; -import { Button, Input, setToast, TOAST_TYPE } from "@plane/ui"; -import { cn, getDate, getDescriptionPlaceholderI18n, renderFormattedPayloadDate } from "@plane/utils"; +import { Button, Input, setToast, TOAST_TYPE, InitiativeIcon, EmojiIconPickerTypes } from "@plane/ui"; +import { getDate, getDescriptionPlaceholderI18n, renderFormattedPayloadDate } from "@plane/utils"; // components +import { Logo } from "@/components/common/logo"; import { DateRangeDropdown } from "@/components/dropdowns/date-range"; import { MemberDropdown } from "@/components/dropdowns/member/dropdown"; import { ProjectDropdown } from "@/components/dropdowns/project/dropdown"; import { RichTextEditor } from "@/components/editor/rich-text"; // hooks -import { useEditorAsset } from "@/hooks/store/use-editor-asset" -import { useMember } from "@/hooks/store/use-member" +import { useEditorAsset } from "@/hooks/store/use-editor-asset"; +import { useMember } from "@/hooks/store/use-member"; import { useWorkspace } from "@/hooks/store/use-workspace"; // plane web hooks import { useEditorMentionSearch } from "@/plane-web/hooks/use-editor-mention-search"; @@ -32,6 +34,8 @@ type Props = { export const CreateUpdateInitiativeForm: FC = (props) => { const { initiativeDetail, formData, isSubmitting, handleFormDataChange, handleClose, handleFormSubmit } = props; + const [isOpen, setIsOpen] = useState(false); + // router const { workspaceSlug } = useParams(); // state @@ -66,6 +70,8 @@ export const CreateUpdateInitiativeForm: FC = (props) => { if (!workspaceSlug || !currentWorkspace || !formData) return null; + const logoValue = formData?.logo_props; + return (
{ @@ -85,7 +91,46 @@ export const CreateUpdateInitiativeForm: FC = (props) => {

{formData.id ? t("initiatives.update_initiative") : t("initiatives.create_initiative")}

-
+
+ setIsOpen(val)} + className="flex items-center justify-center flex-shrink0" + buttonClassName="flex items-center justify-center" + label={ + + <> + {logoValue?.in_use ? ( + + ) : ( + + )} + + + } + onChange={(val: any) => { + let logoValue = {}; + + if (val?.type === "emoji") + logoValue = { + value: val.value, + }; + else if (val?.type === "icon") logoValue = val.value; + + handleFormDataChange("logo_props", { + in_use: val?.type, + [val?.type]: logoValue, + }); + setIsOpen(false); + }} + defaultIconColor={logoValue?.in_use && logoValue?.in_use === "icon" ? logoValue?.icon?.color : undefined} + defaultOpen={ + logoValue?.in_use && logoValue?.in_use === "emoji" + ? EmojiIconPickerTypes.EMOJI + : EmojiIconPickerTypes.ICON + } + />
{ title={initiative.name} itemLink={`/${workspaceSlug}/initiatives/${initiative.id}`} prependTitleElement={ -
- -
+ <> + {initiative?.logo_props?.in_use ? ( + + ) : ( + + )} + } quickActionElement={
diff --git a/apps/web/ee/components/initiatives/details/main/info-section-root.tsx b/apps/web/ee/components/initiatives/details/main/info-section-root.tsx index 1f458afd23..45073f906f 100644 --- a/apps/web/ee/components/initiatives/details/main/info-section-root.tsx +++ b/apps/web/ee/components/initiatives/details/main/info-section-root.tsx @@ -1,11 +1,13 @@ "use client"; -import React, { FC } from "react"; +import React, { FC, useState } from "react"; import { observer } from "mobx-react"; // plane imports +import { EmojiPicker } from "@plane/propel/emoji-icon-picker"; import { EFileAssetType } from "@plane/types"; -import { InitiativeIcon } from "@plane/ui"; +import { EmojiIconPickerTypes, InitiativeIcon } from "@plane/ui"; // plane web components +import { Logo } from "@/components/common/logo"; import { InfoSection } from "@/plane-web/components/common/layout/main/sections/info-root"; import { UpdateStatusPills } from "@/plane-web/components/initiatives/common/update-status"; import { useInitiatives } from "@/plane-web/hooks/store/use-initiatives"; @@ -24,6 +26,7 @@ type Props = { export const InitiativeInfoSection: FC = observer((props) => { const { workspaceSlug, initiativeId, disabled = false, toggleProjectModal, toggleEpicModal } = props; + const [isOpen, setIsOpen] = useState(false); // store hooks const { initiative: { getInitiativeById, updateInitiative, getInitiativeAnalyticsById }, @@ -36,6 +39,8 @@ export const InitiativeInfoSection: FC = observer((props) => { if (!initiative) return <>; + const logoValue = initiative?.logo_props; + return ( = observer((props) => { disabled={disabled} iconElement={
- + setIsOpen(val)} + className="flex items-center justify-center flex-shrink0" + buttonClassName="flex items-center justify-center" + label={ + <> + {logoValue?.in_use ? ( + + ) : ( + + )} + + } + onChange={(val: any) => { + let logoValue = {}; + + if (val?.type === "emoji") + logoValue = { + value: val.value, + }; + else if (val?.type === "icon") logoValue = val.value; + + updateInitiative(workspaceSlug, initiativeId, { + logo_props: { + in_use: val?.type, + [val?.type]: logoValue, + }, + }); + setIsOpen(false); + }} + defaultIconColor={logoValue?.in_use && logoValue?.in_use === "icon" ? logoValue?.icon?.color : undefined} + defaultOpen={ + logoValue?.in_use && logoValue?.in_use === "emoji" + ? EmojiIconPickerTypes.EMOJI + : EmojiIconPickerTypes.ICON + } + />
} titleElement={ diff --git a/apps/web/ee/components/initiatives/header/root.tsx b/apps/web/ee/components/initiatives/header/root.tsx index a0b479020d..c2723c5ae9 100644 --- a/apps/web/ee/components/initiatives/header/root.tsx +++ b/apps/web/ee/components/initiatives/header/root.tsx @@ -18,6 +18,7 @@ import { } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common/breadcrumb-link"; +import { Logo } from "@/components/common/logo"; import { SwitcherLabel } from "@/components/common/switcher-label"; // hooks import { useUserPermissions } from "@/hooks/store/user/user-permissions"; @@ -64,7 +65,12 @@ export const InitiativesDetailsHeader = observer((props: TInitiativesDetailsHead query: _initiative.name, content: ( - + ), }; @@ -131,7 +137,11 @@ export const InitiativesDetailsHeader = observer((props: TInitiativesDetailsHead title={initiativesDetails?.name} icon={ - + {initiativesDetails?.logo_props?.in_use ? ( + + ) : ( + + )} } isLast diff --git a/apps/web/ee/types/initiative/initiative.d.ts b/apps/web/ee/types/initiative/initiative.d.ts index 8580dd192a..df1a010a15 100644 --- a/apps/web/ee/types/initiative/initiative.d.ts +++ b/apps/web/ee/types/initiative/initiative.d.ts @@ -6,6 +6,7 @@ import { InitiativeComment, TIssueActivityUserDetail, TIssueComment, + TLogoProps, } from "@plane/types"; export type TInitiative = { @@ -28,6 +29,7 @@ export type TInitiative = { lead: string | null; project_ids: string[] | null; epic_ids: string[] | null; + logo_props: TLogoProps | undefined; }; export type TInitiativeProject = { diff --git a/packages/constants/src/event-tracker/core.ts b/packages/constants/src/event-tracker/core.ts index cbe52ba107..97337ccf67 100644 --- a/packages/constants/src/event-tracker/core.ts +++ b/packages/constants/src/event-tracker/core.ts @@ -9,6 +9,9 @@ export const GROUP_WORKSPACE_TRACKER_EVENT = "workspace_metrics"; export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected"; export const HEADER_GITHUB_ICON = "header_github_icon"; +export const GITHUB_REDIRECTED_TRACKER_EVENT = "github_redirected"; +export const HEADER_GITHUB_ICON = "header_github_icon"; + /** * =========================================================================== * Command palette tracker