diff --git a/ownercommunity.code-workspace b/ownercommunity.code-workspace index cd106a72..3d500f4c 100644 --- a/ownercommunity.code-workspace +++ b/ownercommunity.code-workspace @@ -24,10 +24,10 @@ ], "settings": { "debug.internalConsoleOptions": "neverOpen", - "github.copilot.chat.codeGeneration.instructions": [ + "github.copilot.chat.experimental.codeGeneration.instructions": [ { "file": "rules.md" - } + }, ] } } \ No newline at end of file diff --git a/ui-community/src/components/layouts/admin/components/community-detail.container.tsx b/ui-community/src/components/layouts/admin/components/community-detail.container.tsx index 5625bd54..44136053 100644 --- a/ui-community/src/components/layouts/admin/components/community-detail.container.tsx +++ b/ui-community/src/components/layouts/admin/components/community-detail.container.tsx @@ -10,13 +10,14 @@ export const CommunityDetailContainer: React.FC = (props) => { loading: communityLoading, error: communityError } = useQuery(AdminCommunityDetailContainerCommunityByIdDocument, { - variables: { id: props.data.id ?? '' } - }); + variables: { id: props.data.id }, + fetchPolicy: 'network-only' + },); return ( } error={communityError} /> diff --git a/ui-community/src/components/layouts/admin/components/community-detail.tsx b/ui-community/src/components/layouts/admin/components/community-detail.tsx index d68204c2..f65fc3ea 100644 --- a/ui-community/src/components/layouts/admin/components/community-detail.tsx +++ b/ui-community/src/components/layouts/admin/components/community-detail.tsx @@ -10,10 +10,10 @@ export interface CommunityDetailProps { export const CommunityDetail: React.FC = (props) => { const whiteLabelDetails = () => { - if (props.data.whiteLabelDomain) { + if (props.data?.whiteLabelDomain) { return ( - {props.data.whiteLabelDomain} + {props.data?.whiteLabelDomain} ); } else { @@ -22,20 +22,20 @@ export const CommunityDetail: React.FC = (props) => { }; const domainDetails = () => { - if (props.data.domain) { + if (props.data?.domain) { return ( - {props.data.domain} + {props.data?.domain} ); } }; const handleDetails = () => { - if (props.data.handle) { + if (props.data?.handle) { return ( - {props.data.handle} + {props.data?.handle} ); } else { @@ -65,10 +65,10 @@ export const CommunityDetail: React.FC = (props) => { - {props.data.id} + {props.data?.id} - {props.data.name} + {props.data?.name} {whiteLabelDetails()} {domainDetails()} diff --git a/ui-community/src/components/layouts/admin/index.tsx b/ui-community/src/components/layouts/admin/index.tsx index 3cefad38..7fb1e8cf 100644 --- a/ui-community/src/components/layouts/admin/index.tsx +++ b/ui-community/src/components/layouts/admin/index.tsx @@ -1,22 +1,7 @@ -import { - BarsOutlined, - ContactsOutlined, - HomeOutlined, - LayoutOutlined, - SafetyOutlined, - ScheduleOutlined, - SettingOutlined -} from '@ant-design/icons'; import { Route, Routes, useParams } from 'react-router-dom'; import { BlobToLocalStorage } from '../../shared/blob-to-local-storage'; -import { Home } from './pages/home'; -import { Members } from './pages/members'; -import { Properties } from './pages/properties'; -import { Roles } from './pages/roles'; -import { ServiceTickets } from './pages/service-tickets'; -import { Settings } from './pages/settings'; -import { SiteEditor } from './pages/site-editor'; import { SectionLayoutContainer } from './section-layout.container'; +import { AdminPage } from './pages/admin-page'; import { Member } from '../../../generated'; export interface PageLayoutProps { @@ -31,82 +16,11 @@ export interface PageLayoutProps { export const Admin: React.FC = (_props) => { const params = useParams(); - const pathLocations = { - home: '', - settings: 'settings/*', - siteEditor: 'site-editor/*', - roles: 'roles/*', - members: 'members/*', - properties: 'properties/*', - serviceTickets: 'service-tickets/*' - }; - - const pageLayouts: PageLayoutProps[] = [ - { path: pathLocations.home, title: 'Home', icon: , id: 'ROOT' }, - { - path: pathLocations.settings, - title: 'Settings', - icon: , - id: 2, - parent: 'ROOT', - hasPermissions: (member: Member) => - member?.role?.permissions?.communityPermissions?.canManageCommunitySettings ?? false - }, - { - path: pathLocations.siteEditor, - title: 'Site Editor', - icon: , - id: 3, - parent: 'ROOT', - hasPermissions: (member: Member) => member?.role?.permissions?.communityPermissions?.canManageSiteContent ?? false - }, - { - path: pathLocations.roles, - title: 'Roles', - icon: , - id: 4, - parent: 'ROOT', - hasPermissions: (member: Member) => - member?.role?.permissions?.communityPermissions?.canManageRolesAndPermissions ?? false - }, - { - path: pathLocations.members, - title: 'Members', - icon: , - id: 5, - parent: 'ROOT', - hasPermissions: (member: Member) => member?.role?.permissions?.communityPermissions?.canManageMembers ?? false - }, - { - path: pathLocations.properties, - title: 'Properties', - icon: , - id: 6, - parent: 'ROOT', - hasPermissions: (member: Member) => member?.role?.permissions?.propertyPermissions?.canManageProperties ?? false - }, - { - path: pathLocations.serviceTickets, - title: 'Tickets', - icon: , - id: 7, - parent: 'ROOT', - hasPermissions: (member: Member) => (member?.role?.permissions?.serviceTicketPermissions?.canManageTickets || - member?.role?.permissions?.violationTicketPermissions?.canManageTickets) ?? false - } - ]; - return ( - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + }> + } /> diff --git a/ui-community/src/components/layouts/admin/pages/admin-page.tsx b/ui-community/src/components/layouts/admin/pages/admin-page.tsx new file mode 100644 index 00000000..6a7ed9af --- /dev/null +++ b/ui-community/src/components/layouts/admin/pages/admin-page.tsx @@ -0,0 +1,214 @@ +import { BarsOutlined, ContactsOutlined, HomeOutlined, LayoutOutlined, SafetyOutlined, ScheduleOutlined, SettingOutlined } from '@ant-design/icons'; +import ProLayout from '@ant-design/pro-layout'; +import { Grid, Menu } from 'antd'; +import { Content } from 'antd/es/layout/layout'; +import React, { useState } from 'react'; +import { Link, Route, Routes, useLocation } from 'react-router-dom'; +import { ServiceTickets } from './service-tickets'; +import { Properties } from './properties'; +import { Settings } from './settings'; +import { SiteEditor } from './site-editor'; +import { Roles } from './roles'; +import { Home } from './home'; +import { Members } from './members'; +import { Member } from '../../../../generated'; + +interface HomeProps {} + +export interface PageLayoutProps { + path: string; + title: string; + icon: React.JSX.Element; + id: string | number; + parent?: string; + hasPermissions?: (member: Member) => boolean; +} + +export const AdminPage: React.FC = () => { + const location = useLocation(); + + const [hoveredId, setHoveredId] = useState(null); + const [clickedId, setClickedId] = useState(null); + const screen = Grid.useBreakpoint(); + const isMobile = screen.xs; + + const MenuContent: React.FC<{ matchedIds: string[]; pageLayouts: any[] }> = ({ matchedIds, pageLayouts }) => { + const createMenuItems = () => { + return pageLayouts.map((layout) => { + const isHovered = hoveredId === layout.id; + const isClicked = clickedId === layout.id; + const isSelected = matchedIds.includes(layout.id); + const icon = + (isHovered || isClicked || isSelected) && layout.filledIcon + ? React.cloneElement(layout.filledIcon, { style: { color: '#3f4373', fontSize: '24px' } }) + : layout.icon + ? React.cloneElement(layout.icon, { style: { fontSize: '24px' } }) + : null; + + return { + key: layout.id, + label: ( + +
{icon}
+
{layout.title}
+ + ), + style: { + paddingTop: '16px', + paddingBottom: '16px', + paddingLeft: '24px', + paddingRight: '24px', + minHeight: '70px', + minWidth: '55px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + textAlign: 'center', + lineHeight: '1.2', + borderRadius: '0', + backgroundColor: isSelected ? '#d3d6f8' : 'transparent', + fontSize: '10px', + width: '100%', + marginInline: '0px', + marginBlock: '0px' + }, + onMouseEnter: () => setHoveredId(layout.id), + onMouseLeave: () => setHoveredId(null), + onClick: () => { + setClickedId(layout.id); + if (isMobile) { + setCollapsed(true); + } + } + }; + }); + }; + + return ( + + ); + }; + + const pathLocations = { + home: '*', + settings: 'settings/*', + siteEditor: 'site-editor/*', + roles: 'roles/*', + members: 'members/*', + properties: 'properties/*', + serviceTickets: 'service-tickets/*' + }; + + const pageLayouts: PageLayoutProps[] = [ + { path: pathLocations.home, title: 'Home', icon: , id: 'ROOT' }, + { + path: pathLocations.settings, + title: 'Settings', + icon: , + id: 2, + parent: 'ROOT' + // hasPermissions: (member: Member) => member?.role?.permissions?.communityPermissions?.canManageCommunitySettings ?? false + }, + { + path: pathLocations.siteEditor, + title: 'Site Editor', + icon: , + id: 3, + parent: 'ROOT' + // hasPermissions: (member: Member) => member?.role?.permissions?.communityPermissions?.canManageSiteContent ?? false + }, + { + path: pathLocations.roles, + title: 'Roles', + icon: , + id: 4, + parent: 'ROOT' + // hasPermissions: (member: Member) => member?.role?.permissions?.communityPermissions?.canManageRolesAndPermissions ?? false + }, + { + path: pathLocations.members, + title: 'Members', + icon: , + id: 5, + parent: 'ROOT' + // hasPermissions: (member: Member) => member?.role?.permissions?.communityPermissions?.canManageMembers ?? false + }, + { + path: pathLocations.properties, + title: 'Properties', + icon: , + id: 6, + parent: 'ROOT' + // hasPermissions: (member: Member) => member?.role?.permissions?.propertyPermissions?.canManageProperties ?? false + }, + { + path: pathLocations.serviceTickets, + title: 'Tickets', + icon: , + id: 7, + parent: 'ROOT' + // hasPermissions: (member: Member) =>(member?.role?.permissions?.serviceTicketPermissions?.canManageTickets || member?.role?.permissions?.violationTicketPermissions?.canManageTickets) ?? false + } + ]; + + const matchPartialRoute = (currentPath: string, routePath: string) => { + return currentPath.startsWith(routePath); + }; + + const getMatchedPageIds = (pageLayouts: any[], location: { pathname: string }) => { + const currentPath = location.pathname; + + return pageLayouts.filter((layout) => matchPartialRoute(currentPath, layout.path)).map((layout) => layout.id.toString()); + }; + + const matchedIds = getMatchedPageIds(pageLayouts, location); + + const [collapsed, setCollapsed] = useState(true); + + return ( + } + menuHeaderRender={() => null} + footerRender={() => null} + collapsedButtonRender={() => null} + onCollapse={() => setCollapsed(!collapsed)} + collapsed={collapsed} + siderWidth={86} + contentStyle={{ + paddingBlock: '0px', + paddingInline: '0px', + height: '100%', + width: '100%', + backgroundColor: '#ffffff', + zIndex: 1 + }} + > + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +}; diff --git a/ui-community/src/components/layouts/admin/pages/home.tsx b/ui-community/src/components/layouts/admin/pages/home.tsx index a5b6239f..f07c32b3 100644 --- a/ui-community/src/components/layouts/admin/pages/home.tsx +++ b/ui-community/src/components/layouts/admin/pages/home.tsx @@ -4,6 +4,7 @@ import { theme } from 'antd'; import { useParams } from 'react-router-dom'; import { CommunityDetailContainer } from '../components/community-detail.container'; import { SubPageLayout } from '../sub-page-layout'; +import { useEffect } from 'react'; export const Home: React.FC = () => { @@ -14,18 +15,21 @@ export const Home: React.FC = () => { }=theme.useToken() const params = useParams(); return ( - Home - } - />}> - - Home - Admin - - - + <> + {params.communityId !== null && ( + Home + } + />}> + + Home - Admin + + + + )} + ); }; diff --git a/ui-community/src/components/layouts/admin/pages/settings.tsx b/ui-community/src/components/layouts/admin/pages/settings.tsx index 11e3806b..a04f9a7a 100644 --- a/ui-community/src/components/layouts/admin/pages/settings.tsx +++ b/ui-community/src/components/layouts/admin/pages/settings.tsx @@ -1,26 +1,30 @@ import { BookOutlined, SettingOutlined } from '@ant-design/icons'; import { PageHeader } from '@ant-design/pro-layout'; -import { theme } from 'antd'; - - +import { Col, Grid, Menu, Row, theme } from 'antd'; import { SubPageLayout } from '../sub-page-layout'; import { SettingsGeneral } from './settings-general'; import { SettingsRoles } from './settings-roles'; import { Helmet } from 'react-helmet-async'; -import { VerticalTabs,RouteDefinition } from '../../../shared/vertical-tabs'; +import { VerticalTabs, RouteDefinition } from '../../../shared/vertical-tabs'; +import { Link, Route, Routes } from 'react-router-dom'; export const Settings: React.FC = () => { const { token: { colorTextBase } } = theme.useToken(); - - - const pages:RouteDefinition[] = [ - { id: "1", link:'', path: '', title: 'General', icon: , element: }, - { id: "2", link:'saml', path: 'saml', title: 'Saml', icon: , element: }, + const pages: RouteDefinition[] = [ + { id: '1', link: '', path: '', title: 'General', icon: , element: }, + { id: '2', link: 'saml', path: 'saml', title: 'Saml', icon: , element: } ]; + const screens = Grid.useBreakpoint(); + const isMobile = screens.xs; + + const profileNavigationMenu = pages.map((page) => ({ + key: page.id, + label: {page.title} + })); return ( = () => { /> } > - + Admin Settings - - + + {isMobile ? ( + + + + + + + {pages.map((page) => ( + + ))} + + + + ) : ( + + )} ); }; diff --git a/ui-community/src/components/layouts/admin/pages/site-editor.tsx b/ui-community/src/components/layouts/admin/pages/site-editor.tsx index 445d91ec..e39df455 100644 --- a/ui-community/src/components/layouts/admin/pages/site-editor.tsx +++ b/ui-community/src/components/layouts/admin/pages/site-editor.tsx @@ -28,7 +28,8 @@ export const SiteEditor: React.FC = () => { {id:'files', path:'files', title:'Files'} ] - const convertedRoutes = useMemo(() => pages.map((x) => {return {id: x.id, path: useResolvedPath(x.path).pathname} as RouteObject}), [pages]); + const resolvedPaths = pages.map((x) => useResolvedPath(x.path).pathname); + const convertedRoutes = useMemo(() => pages.map((x, index) => {return {id: x.id, path: resolvedPaths[index]} as RouteObject}), [pages, resolvedPaths]); const matchedPages = matchRoutes(convertedRoutes, location); const selectedPage = (matchedPages ? matchedPages.map((x:any) => x.route.id.toString()) : ['page-tree'])[0]; diff --git a/ui-community/src/components/layouts/admin/section-layout.container.tsx b/ui-community/src/components/layouts/admin/section-layout.container.tsx index dd5c5ea7..0ec5f542 100644 --- a/ui-community/src/components/layouts/admin/section-layout.container.tsx +++ b/ui-community/src/components/layouts/admin/section-layout.container.tsx @@ -1,16 +1,13 @@ import { useLazyQuery } from '@apollo/client'; -import { PageLayoutProps } from '.'; -import { Member, SectionLayoutContainerMemberByIdQueryDocument } from '../../../generated'; +import { SectionLayoutContainerMemberByIdQueryDocument } from '../../../generated'; import { useParams } from 'react-router-dom'; import { ComponentQueryLoader } from '../../ui/molecules/component-query-loader'; import { SectionLayout } from './section-layout'; -import { useEffect, useState } from 'react'; +import { FC, useEffect, useState } from 'react'; -interface SectionLayoutContainerProps { - pageLayouts: PageLayoutProps[]; -} +interface SectionLayoutContainerProps {} -export const SectionLayoutContainer = (props: SectionLayoutContainerProps) => { +export const SectionLayoutContainer: FC = () => { const params = useParams(); const [memberQuery] = useLazyQuery(SectionLayoutContainerMemberByIdQueryDocument); @@ -20,7 +17,7 @@ export const SectionLayoutContainer = (props: SectionLayoutContainerProps) => { useEffect(() => { const getData = async () => { - try{ + try { const { data: memberDataTemp, loading: memberLoadingTemp, @@ -31,22 +28,14 @@ export const SectionLayoutContainer = (props: SectionLayoutContainerProps) => { } }); setMemberData(memberDataTemp); - setMemberError(memberErrorTemp); - setMemberLoading(memberLoadingTemp); - } - catch(e){ - console.error("Error fetching data in section layout: ", e); + setMemberError(memberErrorTemp); + setMemberLoading(memberLoadingTemp); + } catch (e) { + console.error('Error fetching data in section layout: ', e); } }; getData(); }, [params]); - return ( - } - error={memberError} - /> - ); + return } error={memberError} />; }; diff --git a/ui-community/src/components/layouts/admin/section-layout.tsx b/ui-community/src/components/layouts/admin/section-layout.tsx index 3e952edf..317cd2c8 100644 --- a/ui-community/src/components/layouts/admin/section-layout.tsx +++ b/ui-community/src/components/layouts/admin/section-layout.tsx @@ -1,91 +1,62 @@ import { Layout, theme } from 'antd'; -import React, { useState } from 'react'; +import React from 'react'; import { Link, Outlet, useParams } from 'react-router-dom'; -import { PageLayoutProps } from '.'; -import { LocalSettingsKeys, handleToggler } from '../../../constants'; import { CommunitiesDropdownContainer } from '../../ui/organisms/dropdown-menu/communities-dropdown-container'; import { LoggedInUserContainer } from '../../ui/organisms/header/logged-in-user.container'; -import { MenuComponent } from '../shared/components/menu-component'; import './section-layout.css'; -import { Member } from '../../../generated'; +import { Content } from 'antd/es/layout/layout'; -const { Sider, Header } = Layout; +const { Header } = Layout; -interface AdminSectionLayoutProps { - pageLayouts: PageLayoutProps[]; - memberData: Member; -} +interface AdminSectionLayoutProps {} -export const SectionLayout: React.FC = (props) => { +export const SectionLayout: React.FC = () => { const params = useParams(); - const sidebarCollapsed = localStorage.getItem(LocalSettingsKeys.SidebarCollapsed); - const [isExpanded, setIsExpanded] = useState(!sidebarCollapsed); const { token: { colorBgContainer } } = theme.useToken(); return ( - +
-
-
- -
- - View Member Site - - - -
+ + + View Member Site + +
- - - handleToggler(isExpanded, setIsExpanded)} - style={{ - overflow: 'auto', - height: 'calc(100vh - 64px)', - position: 'relative', - left: 0, - top: 0, - bottom: 0, - backgroundColor: colorBgContainer - }} - > -
- - - - - + - + ); diff --git a/ui-community/src/components/shared/vertical-tabs.tsx b/ui-community/src/components/shared/vertical-tabs.tsx index 87abaccb..93bc242c 100644 --- a/ui-community/src/components/shared/vertical-tabs.tsx +++ b/ui-community/src/components/shared/vertical-tabs.tsx @@ -2,46 +2,47 @@ import { Col, Menu, Row, theme } from 'antd'; import { Link, Route, RouteObject, Routes, matchRoutes, useLocation, useResolvedPath } from 'react-router-dom'; import { useMemo } from 'react'; - export interface RouteDefinition { id: string; - link:string; + link: string; path: string; title: string; icon: React.ReactNode; element: React.ReactNode; } -export const VerticalTabs: React.FC<{pages: RouteDefinition[]}> = ({pages}) => { +export const VerticalTabs: React.FC<{ pages: RouteDefinition[] }> = ({ pages }) => { const location = useLocation(); + const resolvedPaths = pages.map((x) => useResolvedPath(x.path).pathname); const convertedRoutes = useMemo(() => { - return pages.map((x) => { - return { id: x.id, path: useResolvedPath(x.path).pathname} as RouteObject ; + return pages.map((x, index) => { + return { id: x.id, path: resolvedPaths[index] } as RouteObject; }); - }, [pages]); - const matchedPages = matchRoutes(convertedRoutes,location) - const matchedIds = matchedPages ? matchedPages.map((x:any) => x.route.id.toString()) : []; + }, [pages, resolvedPaths]); + const matchedPages = matchRoutes(convertedRoutes, location); + const matchedIds = matchedPages ? matchedPages.map((x: any) => x.route.id.toString()) : []; const { token: { colorTextBase } } = theme.useToken(); return ( - + - - { - pages.map((page) => ( - - {page.title} - - )) - } - + + {pages.map((page) => ( + + {page.title} + + ))} + - + {pages.map((page) => ( @@ -50,4 +51,4 @@ export const VerticalTabs: React.FC<{pages: RouteDefinition[]}> = ({pages}) => { ); -} \ No newline at end of file +}; diff --git a/ui-community/src/components/ui/molecules/logged-in-user/logged-in.tsx b/ui-community/src/components/ui/molecules/logged-in-user/logged-in.tsx index 0958ff5d..e55283ca 100644 --- a/ui-community/src/components/ui/molecules/logged-in-user/logged-in.tsx +++ b/ui-community/src/components/ui/molecules/logged-in-user/logged-in.tsx @@ -1,4 +1,4 @@ -import { Avatar, Button, Image } from 'antd'; +import { Avatar, Button, Grid, Image } from 'antd'; import PropTypes from 'prop-types'; import { FC } from 'react'; import { Link } from 'react-router-dom'; @@ -27,15 +27,26 @@ export type LoggedInPropTypes = PropTypes.InferProps & Co export const LoggedIn: FC = (props) => { const initials = (props.data.firstName.charAt(0) + props.data.lastName.charAt(0)).toUpperCase(); - const profileImage = props.data.profileImage ? : - return
- {initials}{' '}{props.data.firstName} {props.data.lastName}{' '} + const profileImage = props.data.profileImage ? ( + + ) : ( + + ); - - - My Community(s) - + const screen = Grid.useBreakpoint(); + const isMobile = screen.xs; -
-} + return ( +
+ + {initials} + + {!isMobile ? props.data.firstName + ' ' + props.data.lastName : ''} + + + + My Community(s) + +
+ ); +};