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..7a675c48a --- /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.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 943b5b9a3..5467d5113 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, @@ -14,16 +15,18 @@ 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' import { ErrorMessage, Pagination, - ProjectListTabs, + ProjectPageWrapper, ProjectsShowcaseFilter, } from '../../../lib/components' +import { + WorkAppContext, +} from '../../../lib/contexts' import { archiveProjectShowcasePost, createProjectShowcasePost, @@ -52,6 +55,7 @@ import type { ProjectShowcasePostCategory, ProjectShowcasePostFilters, ProjectShowcasePostIndustry, + WorkAppContextModel, } from '../../../lib/models' import styles from './ProjectShowcasePage.module.scss' @@ -65,6 +69,8 @@ const DEFAULT_FILTERS: ProjectShowcasePostFilters = { status: undefined, } +const DEFAULT_PER_PAGE = 10 + const STATUS_OPTIONS: SelectOption[] = [ { label: 'All statuses', value: '' }, { label: 'Draft', value: 'DRAFT' }, @@ -329,7 +335,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') @@ -376,12 +382,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], ) @@ -394,6 +400,17 @@ export const ProjectShowcasePage: FC = () => { [postsResult.posts, sortBy, sortOrder], ) + const { + isAdmin: isAdminUser, + isCopilot, + isManager, + }: WorkAppContextModel = useContext(WorkAppContext) + + const canManageProjectShowcasePosts = useMemo( + () => isAdminUser || isCopilot || isManager, + [isAdminUser, isCopilot, isManager], + ) + const hasProjectId = Boolean(projectId) const isLoading = getProjectShowcaseLoading( postsResult.isLoading, @@ -473,6 +490,7 @@ export const ProjectShowcasePage: FC = () => { const [isRestoring, setIsRestoring] = useState(false) const [isOpeningMediaPicker, setIsOpeningMediaPicker] = useState(false) const confirmation = useConfirmationModal() + // const projectResult = useFetchProject(projectId || undefined) const handleOpenCreateModal = useCallback(() => { setManageMode('create') @@ -724,6 +742,7 @@ export const ProjectShowcasePage: FC = () => { setPage(1) setSortBy('updatedAt') setSortOrder('desc') + setPerPage(DEFAULT_PER_PAGE) }, []) const handleRetry = useCallback(() => { @@ -787,14 +806,20 @@ export const ProjectShowcasePage: FC = () => { [], ) - const pageWrapperActions = useMemo(() => ( - - Actions + + {canManageProjectShowcasePosts ? 'Actions' : ''} + @@ -988,52 +1016,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' ? ( + + ) : ( + + ))} + )} @@ -1184,9 +1216,9 @@ export const ProjectShowcasePage: FC = () => { @@ -1196,9 +1228,9 @@ export const ProjectShowcasePage: FC = () => { @@ -1299,7 +1331,7 @@ export const ProjectShowcasePage: FC = () => { {confirmation.modal} - + ) }