From 6b453cf95dd642eacf609b7ede827f6af3238305 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 1 Jul 2026 07:36:30 +0300 Subject: [PATCH 1/4] PM-5308 show project name --- .../components/PageWrapper/PageWrapper.tsx | 4 +- .../src/lib/components/PageWrapper/index.ts | 2 +- .../ProjectPageWrapper.module.scss | 59 ++++++++++ .../ProjectPageWrapper/ProjectPageWrapper.tsx | 106 ++++++++++++++++++ .../components/ProjectPageWrapper/index.ts | 1 + src/apps/work/src/lib/components/index.ts | 1 + .../ProjectShowcasePage.tsx | 39 +++++-- 7 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.module.scss create mode 100644 src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx create mode 100644 src/apps/work/src/lib/components/ProjectPageWrapper/index.ts diff --git a/src/apps/review/src/lib/components/PageWrapper/PageWrapper.tsx b/src/apps/review/src/lib/components/PageWrapper/PageWrapper.tsx index 718561ba8..c31c7b1f8 100644 --- a/src/apps/review/src/lib/components/PageWrapper/PageWrapper.tsx +++ b/src/apps/review/src/lib/components/PageWrapper/PageWrapper.tsx @@ -14,7 +14,7 @@ import { IconArrowLeft, IconExternalLink } from '../../assets/icons' import styles from './PageWrapper.module.scss' -interface Props { +export interface PageWrapperProps { className?: string pageTitle: string backUrl?: string @@ -25,7 +25,7 @@ interface Props { breadCrumb: BreadCrumbData[] } -export const PageWrapper: FC> = props => ( +export const PageWrapper: FC> = props => (
{props.breadCrumb.length > 0 && ( diff --git a/src/apps/review/src/lib/components/PageWrapper/index.ts b/src/apps/review/src/lib/components/PageWrapper/index.ts index 2b0d52de0..bc12b8b6c 100644 --- a/src/apps/review/src/lib/components/PageWrapper/index.ts +++ b/src/apps/review/src/lib/components/PageWrapper/index.ts @@ -1 +1 @@ -export { default as PageWrapper } from './PageWrapper' +export { default as PageWrapper, type PageWrapperProps } from './PageWrapper' diff --git a/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.module.scss b/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.module.scss new file mode 100644 index 000000000..f7c971c78 --- /dev/null +++ b/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.module.scss @@ -0,0 +1,59 @@ +@import '@libs/ui/styles/includes'; + +.container { + display: flex; + flex-direction: column; + gap: 16px; +} + +.headerRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; + margin-top: 16px; +} + +.sectionTitle { + margin: 0; + color: $black-80; + font-size: 20px; + font-weight: 700; +} + +.headerActions { + display: flex; + align-items: center; + gap: 8px; +} + +.projectTitleActions { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.projectEditLink { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border: 1px solid $black-20; + border-radius: 6px; + color: $black-60; + text-decoration: none; + + &:hover, + &:focus { + border-color: #2a62d5; + color: #2a62d5; + outline: none; + } +} + +.projectEditIcon { + width: 16px; + height: 16px; +} diff --git a/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx b/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx new file mode 100644 index 000000000..d0132e3c9 --- /dev/null +++ b/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx @@ -0,0 +1,106 @@ +import { FC, PropsWithChildren, ReactNode, useContext } from "react"; +import { Link } from "react-router-dom"; + +import { IconOutline } from "~/libs/ui"; +import { PageWrapper, PageWrapperProps } from "~/apps/review/src/lib"; + +import { useFetchProject } from "../../hooks"; +import { ProjectListTabs } from "../ProjectListTabs"; +import { ProjectBillingAccountExpiredNotice } from "../ProjectBillingAccountExpiredNotice"; +import { WorkAppContextModel } from "../../models"; +import { WorkAppContext } from "../../contexts"; +import { checkCanEditProjectDetails, checkCanManageProject } from "../../utils"; + +import styles from './ProjectPageWrapper.module.scss' + +export interface ProjectPageWrapperProps extends PageWrapperProps { + projectId: string | undefined + headerActions?: ReactNode +} + +export const ProjectPageWrapper: FC> = props => { + const workAppContext: WorkAppContextModel = useContext(WorkAppContext) + // const currentUserId = workAppContext.loginUserInfo?.userId === undefined + // || workAppContext.loginUserInfo?.userId === null + // ? undefined + // : String(workAppContext.loginUserInfo.userId) + // const currentUserHandle = toOptionalString(workAppContext.loginUserInfo?.handle) + const projectResult = useFetchProject(props.projectId || undefined) + const canManageProject = !!projectResult.project + && checkCanManageProject( + workAppContext.userRoles, + workAppContext.loginUserInfo?.userId, + projectResult.project, + ) + const canEditProjectDetails = !!projectResult.project + && checkCanEditProjectDetails( + workAppContext.userRoles, + workAppContext.loginUserInfo?.userId, + projectResult.project, + ) + + const pageTitle = projectResult.project?.name + ? projectResult.project.name + : props.pageTitle + + const projectTabs = props.projectId + ? + : undefined + + const billingAccountExpiredNotice = props.projectId + ? ( + + ) + : undefined + const titleAction = props.projectId + ? ( +
+ {canEditProjectDetails + ? ( + + + + ) + : undefined} +
+ ) + : undefined + + return ( + + {billingAccountExpiredNotice} + {projectTabs} + +
+
+

{props.pageTitle}

+ + {props.headerActions && ( +
+ {props.headerActions} +
+ )} +
+
+ + {props.children} +
+ ); +}; diff --git a/src/apps/work/src/lib/components/ProjectPageWrapper/index.ts b/src/apps/work/src/lib/components/ProjectPageWrapper/index.ts new file mode 100644 index 000000000..0cc079c8f --- /dev/null +++ b/src/apps/work/src/lib/components/ProjectPageWrapper/index.ts @@ -0,0 +1 @@ +export { ProjectPageWrapper } from './ProjectPageWrapper' diff --git a/src/apps/work/src/lib/components/index.ts b/src/apps/work/src/lib/components/index.ts index e7cde4499..2fce0be36 100644 --- a/src/apps/work/src/lib/components/index.ts +++ b/src/apps/work/src/lib/components/index.ts @@ -18,6 +18,7 @@ export * from './ErrorMessage' export * from './form' export * from './GroupSuccessModal' export * from './LoadingSpinner' +export * from './ProjectPageWrapper' export * from './Pagination' export * from './ProjectCard' export * from './ProjectBillingAccountExpiredNotice' diff --git a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx index 943b5b9a3..2bcae6ec0 100644 --- a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx +++ b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx @@ -21,7 +21,7 @@ import { BaseModal, Button, LoadingSpinner, useConfirmationModal } from '~/libs/ import { ErrorMessage, Pagination, - ProjectListTabs, + ProjectPageWrapper, ProjectsShowcaseFilter, } from '../../../lib/components' import { @@ -35,6 +35,7 @@ import { updateProjectShowcasePost, } from '../../../lib/services' import { + useFetchProject, useFetchProjectShowcasePostCategories, useFetchProjectShowcasePostIndustries, useFetchProjectShowcasePosts, @@ -52,9 +53,11 @@ import type { ProjectShowcasePostCategory, ProjectShowcasePostFilters, ProjectShowcasePostIndustry, + WorkAppContextModel, } from '../../../lib/models' import styles from './ProjectShowcasePage.module.scss' +import { WorkAppContext } from '../../../lib' type SelectOption = FormSelectOption @@ -473,6 +476,27 @@ export const ProjectShowcasePage: FC = () => { const [isRestoring, setIsRestoring] = useState(false) const [isOpeningMediaPicker, setIsOpeningMediaPicker] = useState(false) const confirmation = useConfirmationModal() + const projectResult = useFetchProject(projectId || undefined) + // const workAppContext: WorkAppContextModel = useContext(WorkAppContext) + // const currentUserId = workAppContext.loginUserInfo?.userId === undefined + // || workAppContext.loginUserInfo?.userId === null + // ? undefined + // : String(workAppContext.loginUserInfo.userId) + // const currentUserHandle = toOptionalString(workAppContext.loginUserInfo?.handle) + + // const canManageProject = !!projectResult.project + // && checkCanManageProject( + // workAppContext.userRoles, + // workAppContext.loginUserInfo?.userId, + // projectResult.project, + // ) + // const canEditProjectDetails = !!projectResult.project + // && checkCanEditProjectDetails( + // workAppContext.userRoles, + // workAppContext.loginUserInfo?.userId, + // projectResult.project, + // ) + const projectName = projectResult.project?.name const handleOpenCreateModal = useCallback(() => { setManageMode('create') @@ -861,23 +885,24 @@ export const ProjectShowcasePage: FC = () => { if (!hasProjectId) { return ( - - + ) } return ( - -
{ {confirmation.modal} - + ) } From 3409bb0efb1dbd3389358335aab50f1bb1caca95 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 1 Jul 2026 08:18:08 +0300 Subject: [PATCH 2/4] PM-5308 - qa fixes --- .../ProjectPageWrapper/ProjectPageWrapper.tsx | 26 ++++++------- .../ProjectShowcasePage.module.scss | 12 +++--- .../ProjectShowcasePage.tsx | 39 +++++-------------- 3 files changed, 28 insertions(+), 49 deletions(-) diff --git a/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx b/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx index d0132e3c9..7a675c48a 100644 --- a/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx +++ b/src/apps/work/src/lib/components/ProjectPageWrapper/ProjectPageWrapper.tsx @@ -1,15 +1,15 @@ -import { FC, PropsWithChildren, ReactNode, useContext } from "react"; -import { Link } from "react-router-dom"; +import { FC, PropsWithChildren, ReactNode, useContext } from 'react' +import { Link } from 'react-router-dom' -import { IconOutline } from "~/libs/ui"; -import { PageWrapper, PageWrapperProps } from "~/apps/review/src/lib"; +import { IconOutline } from '~/libs/ui' +import { PageWrapper, PageWrapperProps } from '~/apps/review/src/lib' -import { useFetchProject } from "../../hooks"; -import { ProjectListTabs } from "../ProjectListTabs"; -import { ProjectBillingAccountExpiredNotice } from "../ProjectBillingAccountExpiredNotice"; -import { WorkAppContextModel } from "../../models"; -import { WorkAppContext } from "../../contexts"; -import { checkCanEditProjectDetails, checkCanManageProject } from "../../utils"; +import { useFetchProject } from '../../hooks' +import { ProjectListTabs } from '../ProjectListTabs' +import { ProjectBillingAccountExpiredNotice } from '../ProjectBillingAccountExpiredNotice' +import { WorkAppContextModel } from '../../models' +import { WorkAppContext } from '../../contexts' +import { checkCanEditProjectDetails, checkCanManageProject } from '../../utils' import styles from './ProjectPageWrapper.module.scss' @@ -32,7 +32,7 @@ export const ProjectPageWrapper: FC> workAppContext.loginUserInfo?.userId, projectResult.project, ) - const canEditProjectDetails = !!projectResult.project + const canEditProjectDetails = !!projectResult.project && checkCanEditProjectDetails( workAppContext.userRoles, workAppContext.loginUserInfo?.userId, @@ -102,5 +102,5 @@ export const ProjectPageWrapper: FC> {props.children} - ); -}; + ) +} diff --git a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.module.scss b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.module.scss index e0e327e5d..fb487f0ad 100644 --- a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.module.scss +++ b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.module.scss @@ -48,6 +48,12 @@ max-width: 100%; } +.loadingRow, +.emptyRow { + text-align: center; + padding: 24px 0; +} + .tableWrap { border: 1px solid $black-10; border-radius: 8px; @@ -247,12 +253,6 @@ padding: 12px 0; } -.loadingRow, -.emptyRow { - text-align: center; - padding: 24px 0; -} - @media (max-width: 1100px) { .filters { grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx index 2bcae6ec0..32f6f6034 100644 --- a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx +++ b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx @@ -14,7 +14,6 @@ import { useParams } from 'react-router-dom' import { SingleValue } from 'react-select' import classNames from 'classnames' -import { PageWrapper } from '~/apps/review/src/lib' import { EnvironmentConfig } from '~/config' import { BaseModal, Button, LoadingSpinner, useConfirmationModal } from '~/libs/ui' @@ -35,7 +34,6 @@ import { updateProjectShowcasePost, } from '../../../lib/services' import { - useFetchProject, useFetchProjectShowcasePostCategories, useFetchProjectShowcasePostIndustries, useFetchProjectShowcasePosts, @@ -53,11 +51,9 @@ import type { ProjectShowcasePostCategory, ProjectShowcasePostFilters, ProjectShowcasePostIndustry, - WorkAppContextModel, } from '../../../lib/models' import styles from './ProjectShowcasePage.module.scss' -import { WorkAppContext } from '../../../lib' type SelectOption = FormSelectOption @@ -68,6 +64,8 @@ const DEFAULT_FILTERS: ProjectShowcasePostFilters = { status: undefined, } +const DEFAULT_PER_PAGE = 10 + const STATUS_OPTIONS: SelectOption[] = [ { label: 'All statuses', value: '' }, { label: 'Draft', value: 'DRAFT' }, @@ -332,7 +330,7 @@ export const ProjectShowcasePage: FC = () => { const [filters, setFilters] = useState(DEFAULT_FILTERS) const [keywordInput, setKeywordInput] = useState('') const [page, setPage] = useState(1) - const [perPage, setPerPage] = useState(10) + const [perPage, setPerPage] = useState(DEFAULT_PER_PAGE) const [sortBy, setSortBy] = useState('updatedAt') const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc') @@ -379,12 +377,12 @@ export const ProjectShowcasePage: FC = () => { }, [keywordInput]) const industryOptions = useMemo( - () => createTaxonomyOptions(industriesResult.items), + () => createTaxonomyOptions(industriesResult.items, 'All Industries'), [industriesResult.items], ) const categoryOptions = useMemo( - () => createTaxonomyOptions(categoriesResult.items), + () => createTaxonomyOptions(categoriesResult.items, 'All Categories'), [categoriesResult.items], ) @@ -476,27 +474,7 @@ export const ProjectShowcasePage: FC = () => { const [isRestoring, setIsRestoring] = useState(false) const [isOpeningMediaPicker, setIsOpeningMediaPicker] = useState(false) const confirmation = useConfirmationModal() - const projectResult = useFetchProject(projectId || undefined) - // const workAppContext: WorkAppContextModel = useContext(WorkAppContext) - // const currentUserId = workAppContext.loginUserInfo?.userId === undefined - // || workAppContext.loginUserInfo?.userId === null - // ? undefined - // : String(workAppContext.loginUserInfo.userId) - // const currentUserHandle = toOptionalString(workAppContext.loginUserInfo?.handle) - - // const canManageProject = !!projectResult.project - // && checkCanManageProject( - // workAppContext.userRoles, - // workAppContext.loginUserInfo?.userId, - // projectResult.project, - // ) - // const canEditProjectDetails = !!projectResult.project - // && checkCanEditProjectDetails( - // workAppContext.userRoles, - // workAppContext.loginUserInfo?.userId, - // projectResult.project, - // ) - const projectName = projectResult.project?.name + // const projectResult = useFetchProject(projectId || undefined) const handleOpenCreateModal = useCallback(() => { setManageMode('create') @@ -748,6 +726,7 @@ export const ProjectShowcasePage: FC = () => { setPage(1) setSortBy('updatedAt') setSortOrder('desc') + setPerPage(DEFAULT_PER_PAGE) }, []) const handleRetry = useCallback(() => { @@ -1209,7 +1188,7 @@ export const ProjectShowcasePage: FC = () => { { Date: Wed, 1 Jul 2026 09:05:57 +0300 Subject: [PATCH 3/4] PM-5309 - qa fixes --- .../ProjectShowcasePage.tsx | 141 +++++++++++------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx index 32f6f6034..91f891b2e 100644 --- a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx +++ b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx @@ -3,6 +3,7 @@ import { ChangeEvent, FC, useCallback, + useContext, useEffect, useMemo, useRef, @@ -23,6 +24,9 @@ import { ProjectPageWrapper, ProjectsShowcaseFilter, } from '../../../lib/components' +import { + WorkAppContext, +} from '../../../lib/contexts' import { archiveProjectShowcasePost, createProjectShowcasePost, @@ -395,6 +399,17 @@ export const ProjectShowcasePage: FC = () => { [postsResult.posts, sortBy, sortOrder], ) + const { + isAdmin: isAdminUser, + isCopilot, + isManager, + } = useContext(WorkAppContext) + + const canManageProjectShowcasePosts = useMemo( + () => isAdminUser || isCopilot || isManager, + [isAdminUser, isCopilot, isManager], + ) + const hasProjectId = Boolean(projectId) const isLoading = getProjectShowcaseLoading( postsResult.isLoading, @@ -790,14 +805,20 @@ export const ProjectShowcasePage: FC = () => { [], ) - const pageWrapperActions = useMemo(() => ( - - Actions + + {canManageProjectShowcasePosts ? 'Actions' : ''} + @@ -992,52 +1015,56 @@ export const ProjectShowcasePage: FC = () => { .join(', ') || '—'} - {post.status !== 'ARCHIVED' && ( - - )} - {post.status === 'DRAFT' && ( - - )} - {post.status === 'PUBLISHED' && ( - - )} - {post.status === 'ARCHIVED' ? ( - - ) : ( - + {canManageProjectShowcasePosts && ( + <> + {post.status !== 'ARCHIVED' && ( + + )} + {post.status === 'DRAFT' && ( + + )} + {post.status === 'PUBLISHED' && ( + + )} + {(post.status === 'ARCHIVED' ? ( + + ) : ( + + ))} + )} @@ -1190,7 +1217,7 @@ export const ProjectShowcasePage: FC = () => { name='industryIds' options={industryOptions.slice(1)} isMulti - isCreatable + isCreatable={isAdminUser} isClearable required /> @@ -1202,7 +1229,7 @@ export const ProjectShowcasePage: FC = () => { name='categoryIds' options={categoryOptions.slice(1)} isMulti - isCreatable + isCreatable={isAdminUser} isClearable required /> From d337ef75f9b8899090cc8a4bc1c585b97a9ef212 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 1 Jul 2026 09:14:20 +0300 Subject: [PATCH 4/4] lint --- .../showcase/ProjectShowcasePage/ProjectShowcasePage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx index 91f891b2e..5467d5113 100644 --- a/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx +++ b/src/apps/work/src/pages/showcase/ProjectShowcasePage/ProjectShowcasePage.tsx @@ -55,6 +55,7 @@ import type { ProjectShowcasePostCategory, ProjectShowcasePostFilters, ProjectShowcasePostIndustry, + WorkAppContextModel, } from '../../../lib/models' import styles from './ProjectShowcasePage.module.scss' @@ -403,7 +404,7 @@ export const ProjectShowcasePage: FC = () => { isAdmin: isAdminUser, isCopilot, isManager, - } = useContext(WorkAppContext) + }: WorkAppContextModel = useContext(WorkAppContext) const canManageProjectShowcasePosts = useMemo( () => isAdminUser || isCopilot || isManager, @@ -807,7 +808,7 @@ export const ProjectShowcasePage: FC = () => { const pageWrapperActions = useMemo(() => { if (!canManageProjectShowcasePosts) { - return null + return <> } return (