diff --git a/view/components/layout/app-sidebar.tsx b/view/components/layout/app-sidebar.tsx index 7b631fec6..48b2dc466 100644 --- a/view/components/layout/app-sidebar.tsx +++ b/view/components/layout/app-sidebar.tsx @@ -1,7 +1,14 @@ 'use client'; import * as React from 'react'; -import { Folder, Home, Package, SettingsIcon, Container } from 'lucide-react'; +import { + Folder, + Home, + Package, + SettingsIcon, + Container, + LucideProps +} from 'lucide-react'; import { NavMain } from '@/components/layout/nav-main'; import { NavUser } from '@/components/layout/nav-user'; import { TeamSwitcher } from '@/components/ui/team-switcher'; @@ -19,7 +26,60 @@ import { setActiveOrganization } from '@/redux/features/users/userSlice'; import { useTranslation } from '@/hooks/use-translation'; import { useRBAC } from '@/lib/rbac'; -const data = { +// Add proper TypeScript interfaces + +// 1. Defined specific types for User and Organization +interface User { + id: string; + name?: string; + email?: string; + // Add other user properties as needed +} + +interface Organization { + id: string; + name?: string; + // Add other organization properties as needed +} + +// 2. Updated AppState to use the specific types instead of 'any' +interface AppState { + auth: { + user: User | null; + }; + user: { + organizations: { organization: Organization }[]; + activeOrganization: Organization | null; + }; +} + +// 3. Created a strict type for resource strings for type safety +type Resource = + | 'dashboard' + | 'deploy' + | 'container' + | 'file-manager' + | 'settings' + | 'notification' + | 'organization' + | 'domain'; + +interface NavSubItem { + title: string; + url: string; + resource: Resource; +} + +// 4. Updated NavItem to use the Resource type and a more specific icon type +interface NavItem { + title: string; + url: string; + icon: React.ComponentType; + resource: Resource; + items?: NavSubItem[]; +} + +const data: { navMain: NavItem[] } = { navMain: [ { title: 'navigation.dashboard', @@ -76,23 +136,21 @@ const data = { ] }; -export function AppSidebar({ - toggleAddTeamModal, - ...props -}: React.ComponentProps & { toggleAddTeamModal?: () => void }) { +// 5. Removed the unused 'toggleAddTeamModal' prop +export function AppSidebar(props: React.ComponentProps) { const { t } = useTranslation(); - const user = useAppSelector((state) => state.auth.user); - const { isLoading, refetch } = useGetUserOrganizationsQuery(); - const organizations = useAppSelector((state) => state.user.organizations); + const user = useAppSelector((state: AppState) => state.auth.user); + const { refetch } = useGetUserOrganizationsQuery(); + const organizations = useAppSelector((state: AppState) => state.user.organizations); const { activeNav, setActiveNav } = useNavigationState(); - const activeOrg = useAppSelector((state) => state.user.activeOrganization); + const activeOrg = useAppSelector((state: AppState) => state.user.activeOrganization); const dispatch = useAppDispatch(); const { canAccessResource } = useRBAC(); const hasAnyPermission = React.useMemo(() => { - const allowedResources = ['dashboard', 'settings']; + const allowedResources: Resource[] = ['dashboard', 'settings']; - return (resource: string) => { + return (resource: Resource) => { if (!user || !activeOrg) return false; if (allowedResources.includes(resource)) { @@ -100,39 +158,40 @@ export function AppSidebar({ } return ( - canAccessResource(resource as any, 'read') || - canAccessResource(resource as any, 'create') || - canAccessResource(resource as any, 'update') || - canAccessResource(resource as any, 'delete') + canAccessResource(resource, 'read') || + canAccessResource(resource, 'create') || + canAccessResource(resource, 'update') || + canAccessResource(resource, 'delete') ); }; }, [user, activeOrg, canAccessResource]); - const filteredNavItems = React.useMemo( - () => - data.navMain - .filter((item) => { - if (!item.resource) return false; - - if (item.items) { - const filteredSubItems = item.items.filter( - (subItem) => subItem.resource && hasAnyPermission(subItem.resource) - ); - return filteredSubItems.length > 0; - } - - return hasAnyPermission(item.resource); - }) - .map((item) => ({ - ...item, - title: t(item.title), - items: item.items?.map((subItem) => ({ + // 6. Corrected and optimized the logic for filtering and mapping navigation items + const filteredNavItems = React.useMemo(() => { + return data.navMain.reduce((acc, item) => { + let visibleSubItems: NavSubItem[] | undefined; + + if (item.items) { + visibleSubItems = item.items + .filter(subItem => hasAnyPermission(subItem.resource)) + .map(subItem => ({ ...subItem, title: t(subItem.title) - })) - })), - [data.navMain, hasAnyPermission, t] - ); + })); + } + + // An item is visible if it has direct permission OR it has visible sub-items + if (hasAnyPermission(item.resource) || (visibleSubItems && visibleSubItems.length > 0)) { + acc.push({ + ...item, + title: t(item.title), + items: visibleSubItems + }); + } + + return acc; + }, []); + }, [hasAnyPermission, t]); React.useEffect(() => { if (organizations && organizations.length > 0 && !activeOrg) { @@ -146,6 +205,77 @@ export function AppSidebar({ } }, [activeOrg?.id, refetch]); + const handleSponsorClick = React.useCallback((): void => { + window.open('https://github.com/sponsors/raghavyuva', '_blank'); + }, []); + + const handleHelpClick = React.useCallback((): void => { + window.open('https://docs.nixopus.com', '_blank'); + }, []); + + const handleReportIssueClick = React.useCallback((): void => { + const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : ''; + const browser = userAgent.includes('Chrome') + ? 'Chrome' + : userAgent.includes('Firefox') + ? 'Firefox' + : userAgent.includes('Safari') + ? 'Safari' + : userAgent.includes('Edge') + ? 'Edge' + : 'Unknown'; + + const os = userAgent.includes('Windows') + ? 'Windows' + : userAgent.includes('Mac') + ? 'macOS' + : userAgent.includes('Linux') + ? 'Linux' + : userAgent.includes('Android') + ? 'Android' + : userAgent.includes('iPhone') || userAgent.includes('iPad') + ? 'iOS' + : 'Unknown'; + + const screenResolution = + typeof screen !== 'undefined' ? `${screen.width}x${screen.height}` : 'Unknown'; + const language = typeof navigator !== 'undefined' ? navigator.language : 'Unknown'; + const timezone = + typeof Intl !== 'undefined' && Intl.DateTimeFormat + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : 'Unknown'; + + const issueBody = `**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +- Browser: ${browser} +- Operating System: ${os} +- Screen Resolution: ${screenResolution} +- Language: ${language} +- Timezone: ${timezone} +- User Agent: ${userAgent} + +Add any other context about the problem here.`; + + const encodedBody = encodeURIComponent(issueBody); + const url = `https://github.com/raghavyuva/nixopus/issues/new?template=bug_report.md&body=${encodedBody}`; + window.open(url, '_blank'); + }, []); + if (!user || !activeOrg) { return null; } @@ -156,21 +286,49 @@ export function AppSidebar({ + {/* 7. Simplified NavMain props, as filtering is now handled correctly in useMemo */} ({ ...item, - isActive: item.url === activeNav, - items: item.items?.filter( - (subItem) => subItem.resource && hasAnyPermission(subItem.resource) - ) + isActive: item.url === activeNav }))} - onItemClick={(url) => setActiveNav(url)} + onItemClick={(url: string) => setActiveNav(url)} /> - +
+ +
+ +
+
+ + + + + +
+
); -} +} \ No newline at end of file diff --git a/view/components/layout/nav-user.tsx b/view/components/layout/nav-user.tsx index 26c74037d..fb3f89ef3 100644 --- a/view/components/layout/nav-user.tsx +++ b/view/components/layout/nav-user.tsx @@ -1,6 +1,6 @@ 'use client'; -import { AlertCircle, ChevronsUpDown, HelpCircle, Heart, LogOut } from 'lucide-react'; +import { ChevronsUpDown, LogOut } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; @@ -83,82 +83,6 @@ export function NavUser({ user }: { user: User }) { } }; - const handleSponsor = () => { - window.open('https://github.com/sponsors/raghavyuva', '_blank'); - }; - - const getClientInfo = () => { - const userAgent = navigator.userAgent; - const browser = userAgent.includes('Chrome') - ? 'Chrome' - : userAgent.includes('Firefox') - ? 'Firefox' - : userAgent.includes('Safari') - ? 'Safari' - : userAgent.includes('Edge') - ? 'Edge' - : 'Unknown'; - - const os = userAgent.includes('Windows') - ? 'Windows' - : userAgent.includes('Mac') - ? 'macOS' - : userAgent.includes('Linux') - ? 'Linux' - : userAgent.includes('Android') - ? 'Android' - : userAgent.includes('iOS') - ? 'iOS' - : 'Unknown'; - - return { - browser, - os, - userAgent, - screenResolution: `${screen.width}x${screen.height}`, - language: navigator.language, - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone - }; - }; - - const handleReportIssue = () => { - const clientInfo = getClientInfo(); - - const issueBody = `**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Additional context** -- Browser: ${clientInfo.browser} -- Operating System: ${clientInfo.os} -- Screen Resolution: ${clientInfo.screenResolution} -- Language: ${clientInfo.language} -- Timezone: ${clientInfo.timezone} -- User Agent: ${clientInfo.userAgent} - -Add any other context about the problem here.`; - - const encodedBody = encodeURIComponent(issueBody); - const url = `https://github.com/raghavyuva/nixopus/issues/new?template=bug_report.md&body=${encodedBody}`; - window.open(url, '_blank'); - }; - - const handleHelp = () => { - window.open('https://docs.nixopus.com', '_blank'); - }; - return ( @@ -212,19 +136,6 @@ Add any other context about the problem here.`; - - - {t('user.menu.sponsor')} - - - - {t('user.menu.help')} - - - - {t('user.menu.reportIssue')} - - {t('user.menu.logout')}