From 97b267e7aefbd25f9908c58dce92f89c46fc69f1 Mon Sep 17 00:00:00 2001 From: Adrian Astles <49412215+adrianeastles@users.noreply.github.com> Date: Sat, 19 Jul 2025 00:54:56 +0800 Subject: [PATCH] Improved resource card layout and favicon handling --- server/routers/resource/getUserResources.ts | 14 +- src/app/[orgId]/MemberResourcesPortal.tsx | 289 ++++++++++++++++---- 2 files changed, 250 insertions(+), 53 deletions(-) diff --git a/server/routers/resource/getUserResources.ts b/server/routers/resource/getUserResources.ts index 87892f67..681ec4d0 100644 --- a/server/routers/resource/getUserResources.ts +++ b/server/routers/resource/getUserResources.ts @@ -9,7 +9,8 @@ import { roles, resourcePassword, resourcePincode, - resourceWhitelist + resourceWhitelist, + sites } from "@server/db"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; @@ -94,9 +95,11 @@ export async function getUserResources( enabled: resources.enabled, sso: resources.sso, protocol: resources.protocol, - emailWhitelistEnabled: resources.emailWhitelistEnabled + emailWhitelistEnabled: resources.emailWhitelistEnabled, + siteName: sites.name }) .from(resources) + .leftJoin(sites, eq(sites.siteId, resources.siteId)) .where( and( inArray(resources.resourceId, accessibleResourceIds), @@ -124,7 +127,12 @@ export async function getUserResources( domain: `${resource.ssl ? "https://" : "http://"}${resource.fullDomain}`, enabled: resource.enabled, protected: !!(resource.sso || hasPassword || hasPincode || hasWhitelist), - protocol: resource.protocol + protocol: resource.protocol, + sso: resource.sso, + password: hasPassword, + pincode: hasPincode, + whitelist: hasWhitelist, + siteName: resource.siteName }; }) ); diff --git a/src/app/[orgId]/MemberResourcesPortal.tsx b/src/app/[orgId]/MemberResourcesPortal.tsx index 3c525e81..142d5516 100644 --- a/src/app/[orgId]/MemberResourcesPortal.tsx +++ b/src/app/[orgId]/MemberResourcesPortal.tsx @@ -7,12 +7,20 @@ 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 } from "lucide-react"; +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"; 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"; +// Update Resource type to include site information type Resource = { resourceId: number; name: string; @@ -20,6 +28,13 @@ type Resource = { enabled: boolean; protected: boolean; protocol: string; + // Auth method fields + sso?: boolean; + password?: boolean; + pincode?: boolean; + whitelist?: boolean; + // Site information + siteName?: string | null; }; type MemberResourcesPortalProps = { @@ -66,30 +81,184 @@ const ResourceFavicon = ({ domain, enabled }: { domain: string; enabled: boolean }; // Enhanced status badge component -const StatusBadge = ({ enabled, protected: isProtected }: { enabled: boolean; protected: boolean }) => { +const StatusBadge = ({ enabled, protected: isProtected, resource }: { enabled: boolean; protected: boolean; resource: Resource }) => { if (!enabled) { return ( - + + + +
- Disabled - +
+
+ +

Resource Disabled

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

Protected Resource

+
+

Authentication Methods:

+
+ {resource.sso && ( +
+
+ +
+ Single Sign-On (SSO) +
+ )} + {resource.password && ( +
+
+ +
+ Password Protected +
+ )} + {resource.pincode && ( +
+
+ +
+ PIN Code +
+ )} + {resource.whitelist && ( +
+
+ +
+ Email Whitelist +
+ )} +
+
+
+
+
); } return ( - - - Unprotected - +
+ +
+ ); +}; + +// Resource Info component +const ResourceInfo = ({ resource }: { resource: Resource }) => { + const hasAuthMethods = resource.sso || resource.password || resource.pincode || resource.whitelist; + + return ( + + + +
+ +
+
+ + {/* 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 ( + + + +
+ +
+
+ +

{resource.siteName}

+
+
+
); }; @@ -212,6 +381,7 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr const t = useTranslations(); const { env } = useEnvContext(); const api = createApiClient({ env }); + const { toast } = useToast(); const [resources, setResources] = useState([]); const [filteredResources, setFilteredResources] = useState([]); @@ -477,53 +647,72 @@ export default function MemberResourcesPortal({ orgId }: MemberResourcesPortalPr ) : ( <> {/* Resources Grid */} -
+
{paginatedResources.map((resource) => ( - - -
- - {resource.name} - - - Your Site - -
-
- -
- {/* Resource URL with Favicon */} -
- - + +
+
+
+
+ +
+ + + + + {resource.name} + + + +

{resource.name}

+
+
+
- - {/* Enhanced Status Badge */} -
- + +
+
- {/* Open Resource Button */} -
- +
- +
+ +
+ +
))}