diff --git a/src/app/[orgId]/MemberResourcesPortal.tsx b/src/app/[orgId]/MemberResourcesPortal.tsx index 142d5516..ad412b1e 100644 --- a/src/app/[orgId]/MemberResourcesPortal.tsx +++ b/src/app/[orgId]/MemberResourcesPortal.tsx @@ -4,21 +4,42 @@ import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { ExternalLink, Globe, ShieldCheck, Search, RefreshCw, AlertCircle, Plus, Shield, ShieldOff, ChevronLeft, ChevronRight, Building2, Key, KeyRound, Fingerprint, AtSign, Copy, InfoIcon } from "lucide-react"; import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from "@/components/ui/select"; +import { + ExternalLink, + Globe, + Search, + RefreshCw, + AlertCircle, + ChevronLeft, + ChevronRight, + Key, + KeyRound, + Fingerprint, + AtSign, + Copy, + InfoIcon, + Combine +} from "lucide-react"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { GetUserResourcesResponse } from "@server/routers/resource/getUserResources"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { useToast } from "@app/hooks/useToast"; +import { InfoPopup } from "@/components/ui/info-popup"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from "@/components/ui/tooltip"; // Update Resource type to include site information type Resource = { @@ -42,26 +63,34 @@ type MemberResourcesPortalProps = { }; // Favicon component with fallback -const ResourceFavicon = ({ domain, enabled }: { domain: string; enabled: boolean }) => { +const ResourceFavicon = ({ + domain, + enabled +}: { + domain: string; + enabled: boolean; +}) => { const [faviconError, setFaviconError] = useState(false); const [faviconLoaded, setFaviconLoaded] = useState(false); - + // Extract domain for favicon URL - const cleanDomain = domain.replace(/^https?:\/\//, '').split('/')[0]; + const cleanDomain = domain.replace(/^https?:\/\//, "").split("/")[0]; const faviconUrl = `https://www.google.com/s2/favicons?domain=${cleanDomain}&sz=32`; - + const handleFaviconLoad = () => { setFaviconLoaded(true); setFaviconError(false); }; - + const handleFaviconError = () => { setFaviconError(true); setFaviconLoaded(false); }; if (faviconError || !enabled) { - return ; + return ( + + ); } return ( @@ -72,7 +101,7 @@ const ResourceFavicon = ({ domain, enabled }: { domain: string; enabled: boolean {`${cleanDomain} @@ -80,198 +109,107 @@ const ResourceFavicon = ({ domain, enabled }: { domain: string; enabled: boolean ); }; -// Enhanced status badge component -const StatusBadge = ({ enabled, protected: isProtected, resource }: { enabled: boolean; protected: boolean; resource: Resource }) => { - if (!enabled) { - return ( - - - -
-
-
-
- -

Resource Disabled

-
-
-
- ); - } - - if (isProtected) { - return ( - - - -
- -
-
- -

Protected Resource

-
-

Authentication Methods:

-
- {resource.sso && ( -
-
- -
- Single Sign-On (SSO) -
- )} - {resource.password && ( -
-
- -
- Password Protected -
- )} - {resource.pincode && ( -
-
- -
- PIN Code -
- )} - {resource.whitelist && ( -
-
- -
- Email Whitelist -
- )} -
-
-
-
-
- ); - } - - return ( -
- -
- ); -}; - // Resource Info component const ResourceInfo = ({ resource }: { resource: Resource }) => { - const hasAuthMethods = resource.sso || resource.password || resource.pincode || resource.whitelist; - - return ( - - - -
- + const hasAuthMethods = + resource.sso || + resource.password || + resource.pincode || + resource.whitelist; + + const infoContent = ( +
+ {/* Site Information */} + {resource.siteName && ( +
+
Site
+
+ + {resource.siteName}
- - - {/* Site Information */} - {resource.siteName && ( -
-
Site
-
- - {resource.siteName} -
-
- )} +
+ )} - {/* Authentication Methods */} - {hasAuthMethods && ( -
-
Authentication Methods
-
- {resource.sso && ( -
-
- -
- Single Sign-On (SSO) -
- )} - {resource.password && ( -
-
- -
- Password Protected -
- )} - {resource.pincode && ( -
-
- -
- PIN Code -
- )} - {resource.whitelist && ( -
-
- -
- Email Whitelist -
- )} -
-
- )} - - {/* Resource Status - if disabled */} - {!resource.enabled && ( -
-
- - Resource Disabled -
-
- )} - - - - ); -}; - -// Site badge component -const SiteBadge = ({ resource }: { resource: Resource }) => { - if (!resource.siteName) { - return null; - } - - return ( - - - -
- + {/* Authentication Methods */} + {hasAuthMethods && ( +
+
+ Authentication Methods
- - -

{resource.siteName}

-
- - +
+ {resource.sso && ( +
+
+ +
+ + Single Sign-On (SSO) + +
+ )} + {resource.password && ( +
+
+ +
+ + Password Protected + +
+ )} + {resource.pincode && ( +
+
+ +
+ PIN Code +
+ )} + {resource.whitelist && ( +
+
+ +
+ Email Whitelist +
+ )} +
+
+ )} + + {/* Resource Status - if disabled */} + {!resource.enabled && ( +
+
+ + + Resource Disabled + +
+
+ )} +
); + + return {infoContent}; }; // Pagination component -const PaginationControls = ({ - currentPage, - totalPages, +const PaginationControls = ({ + currentPage, + totalPages, onPageChange, totalItems, - itemsPerPage -}: { - currentPage: number; - totalPages: number; + itemsPerPage +}: { + currentPage: number; + totalPages: number; onPageChange: (page: number) => void; totalItems: number; itemsPerPage: number; @@ -286,7 +224,7 @@ const PaginationControls = ({
Showing {startItem}-{endItem} of {totalItems} resources
- +
- +
- {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => { - // Show first page, last page, current page, and 2 pages around current - const showPage = - page === 1 || - page === totalPages || - Math.abs(page - currentPage) <= 1; - - const showEllipsis = - (page === 2 && currentPage > 4) || - (page === totalPages - 1 && currentPage < totalPages - 3); + {Array.from({ length: totalPages }, (_, i) => i + 1).map( + (page) => { + // Show first page, last page, current page, and 2 pages around current + const showPage = + page === 1 || + page === totalPages || + Math.abs(page - currentPage) <= 1; - if (!showPage && !showEllipsis) return null; + const showEllipsis = + (page === 2 && currentPage > 4) || + (page === totalPages - 1 && + currentPage < totalPages - 3); + + if (!showPage && !showEllipsis) return null; + + if (showEllipsis) { + return ( + + ... + + ); + } - if (showEllipsis) { return ( - - ... - + ); } - - return ( - - ); - })} + )}
- +
- + {/* Sort */}
@@ -595,7 +567,9 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr disabled={refreshing} className="gap-2 shrink-0" > - + Refresh
@@ -603,7 +577,7 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr {/* Resources Content */} {filteredResources.length === 0 ? ( /* Enhanced Empty State */ - +
{searchQuery ? ( @@ -613,17 +587,18 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr )}

- {searchQuery ? "No Resources Found" : "No Resources Available"} + {searchQuery + ? "No Resources Found" + : "No Resources Available"}

- {searchQuery + {searchQuery ? `No resources match "${searchQuery}". Try adjusting your search terms or clearing the search to see all resources.` - : "You don't have access to any resources yet. Contact your administrator to get access to resources you need." - } + : "You don't have access to any resources yet. Contact your administrator to get access to resources you need."}

{searchQuery ? ( - ) : ( - )} @@ -649,12 +626,15 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr {/* Resources Grid */}
{paginatedResources.map((resource) => ( - +
- +
@@ -664,12 +644,14 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr -

{resource.name}

+

+ {resource.name} +

- +
@@ -677,21 +659,29 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr
-
); -} \ No newline at end of file +} diff --git a/src/app/[orgId]/page.tsx b/src/app/[orgId]/page.tsx index 9a1dda94..4740198b 100644 --- a/src/app/[orgId]/page.tsx +++ b/src/app/[orgId]/page.tsx @@ -62,11 +62,7 @@ export default async function OrgPage(props: OrgPageProps) { return ( - {overview && ( -
- -
- )} + {overview && }
); diff --git a/src/components/ui/info-popup.tsx b/src/components/ui/info-popup.tsx index 732c23e9..cff1cce4 100644 --- a/src/components/ui/info-popup.tsx +++ b/src/components/ui/info-popup.tsx @@ -11,11 +11,12 @@ import { Button } from "@/components/ui/button"; interface InfoPopupProps { text?: string; - info: string; + info?: string; trigger?: React.ReactNode; + children?: React.ReactNode; } -export function InfoPopup({ text, info, trigger }: InfoPopupProps) { +export function InfoPopup({ text, info, trigger, children }: InfoPopupProps) { const defaultTrigger = (