diff --git a/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css b/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css index e74ba24..93cb4ac 100644 --- a/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css +++ b/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css @@ -28,13 +28,13 @@ position: fixed; z-index: 1200; transform: translateX(-100%); - min-width: 168px; + min-width: 220px; padding: 6px; border-radius: 10px; - border: 1px solid var(--glass-border); - background: rgba(255, 255, 255, 0.92); - backdrop-filter: blur(24px); - -webkit-backdrop-filter: blur(24px); + border: 1px solid rgba(255, 255, 255, 0.55); + background: rgba(255, 255, 255, 0.28); + backdrop-filter: blur(48px) saturate(1.2); + -webkit-backdrop-filter: blur(48px) saturate(1.2); box-shadow: var(--glass-shadow); animation: menuIn 0.14s ease; } @@ -53,11 +53,34 @@ transition: background 0.15s, color 0.15s; } +.itemText { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + flex: 1; +} + +.itemDetail { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 11px; + font-weight: 500; + color: var(--text-muted); + line-height: 1.2; + word-break: break-all; +} + .item:hover { background: rgba(var(--primary-rgb) / 0.08); color: var(--primary); } +.item:hover .itemDetail { + color: var(--primary-soft); +} + .itemDanger:hover { background: rgba(239, 68, 68, 0.08); color: #ef4444; diff --git a/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx b/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx new file mode 100644 index 0000000..4656d0b --- /dev/null +++ b/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx @@ -0,0 +1,176 @@ +import { useEffect, useRef, useState } from 'react' +import { createPortal } from 'react-dom' +import { Building2, ExternalLink, Globe, MoreHorizontal, UserRound } from 'lucide-react' +import styles from './BusinessActionsOverflowMenu.module.css' + +type PopoverPosition = { + top: number + left: number +} + +interface DomainLinksOverflowMenuProps { + host: string +} + +function apexHost(host: string) { + return host.trim().replace(/^www\./i, '') +} + +function siteProtocol(host: string) { + return /\.local$/i.test(host) ? 'http:' : 'https:' +} + +function siteUrl(host: string) { + return `${siteProtocol(host)}//${host}` +} + +function openHost(host: string) { + window.open(siteUrl(host), '_blank', 'noopener,noreferrer') +} + +export function DomainLinksOverflowMenu({ host }: DomainLinksOverflowMenuProps) { + const [open, setOpen] = useState(false) + const [popoverPos, setPopoverPos] = useState(null) + const wrapRef = useRef(null) + const popoverRef = useRef(null) + const triggerRef = useRef(null) + + const apex = apexHost(host) + const websiteHost = apex + const businessHost = `business.${apex}` + const customerHost = `customer.${apex}` + + function updatePopoverPosition() { + const rect = triggerRef.current?.getBoundingClientRect() + if (!rect) return + + setPopoverPos({ + top: rect.bottom + 8, + left: rect.right, + }) + } + + function toggleOpen() { + if (open) { + setOpen(false) + return + } + + updatePopoverPosition() + setOpen(true) + } + + useEffect(() => { + if (!open) return + + function handleClickOutside(e: MouseEvent) { + const target = e.target as Node + if (wrapRef.current?.contains(target)) return + if (popoverRef.current?.contains(target)) return + setOpen(false) + } + + function handleEscape(e: KeyboardEvent) { + if (e.key === 'Escape') setOpen(false) + } + + function handleReposition() { + updatePopoverPosition() + } + + document.addEventListener('mousedown', handleClickOutside) + document.addEventListener('keydown', handleEscape) + window.addEventListener('resize', handleReposition) + window.addEventListener('scroll', handleReposition, true) + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + document.removeEventListener('keydown', handleEscape) + window.removeEventListener('resize', handleReposition) + window.removeEventListener('scroll', handleReposition, true) + } + }, [open]) + + const items = [ + { + key: 'website', + label: 'Open website', + detail: websiteHost, + icon: Globe, + onClick: () => openHost(websiteHost), + }, + { + key: 'business', + label: 'Business dashboard', + detail: businessHost, + icon: Building2, + onClick: () => openHost(businessHost), + }, + { + key: 'customer', + label: 'Customer dashboard', + detail: customerHost, + icon: UserRound, + onClick: () => openHost(customerHost), + }, + ] as const + + return ( +
+ + + {open && + popoverPos && + createPortal( +
+ {items.map(({ key, label, detail, icon: Icon, onClick }) => ( + + ))} +
, + document.body, + )} +
+ ) +} diff --git a/apps/super-admin/src/components/Header.tsx b/apps/super-admin/src/components/Header.tsx index ecb1675..bad496e 100644 --- a/apps/super-admin/src/components/Header.tsx +++ b/apps/super-admin/src/components/Header.tsx @@ -14,7 +14,7 @@ export function Header() { const menuRef = useRef(null) const displayName = - [user?.firstName, user?.lastName].filter(Boolean).join(' ') || + [user?.firstNameEn, user?.lastNameEn].filter(Boolean).join(' ') || user?.cellNumber || 'Super Admin' diff --git a/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css b/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css index 9c6d1db..244a583 100644 --- a/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css +++ b/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css @@ -50,10 +50,10 @@ max-width: 280px; padding: 8px 10px; border-radius: var(--radius-sm); - border: 1px solid var(--glass-border); - background: var(--glass-bg); - backdrop-filter: blur(var(--blur-glass)); - -webkit-backdrop-filter: blur(var(--blur-glass)); + border: 1px solid rgba(255, 255, 255, 0.55); + background: rgba(255, 255, 255, 0.28); + backdrop-filter: blur(48px) saturate(1.2); + -webkit-backdrop-filter: blur(48px) saturate(1.2); box-shadow: var(--glass-shadow); } diff --git a/apps/super-admin/src/pages/HomePage.tsx b/apps/super-admin/src/pages/HomePage.tsx index 6254224..9f2e71b 100644 --- a/apps/super-admin/src/pages/HomePage.tsx +++ b/apps/super-admin/src/pages/HomePage.tsx @@ -45,15 +45,13 @@ function getFormattedDate() { export function HomePage() { const { user } = useAuth() - const firstName = user?.firstName || 'Admin' + const firstName = user?.firstNameEn || 'Admin' return (
-

- Welcome back, {firstName}! -

+

Welcome back, {firstName}!

Here's what's happening across the Meshkee platform today.

diff --git a/apps/super-admin/src/pages/WebsitesPage.tsx b/apps/super-admin/src/pages/WebsitesPage.tsx index ec8a126..2d9cf63 100644 --- a/apps/super-admin/src/pages/WebsitesPage.tsx +++ b/apps/super-admin/src/pages/WebsitesPage.tsx @@ -13,6 +13,7 @@ import { Unlock, } from 'lucide-react' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' +import { DomainLinksOverflowMenu } from '../components/DomainLinksOverflowMenu' import { Modal } from '../components/Modal' import { ToggleSwitch } from '../components/ToggleSwitch' import type { DomainListItem, DomainsListResponse } from '../types/domain' @@ -589,6 +590,7 @@ export function WebsitesPage() { > +