From 6771d75fae85fa4bc4fe5ae3d58d8deedf84b10b Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Fri, 7 Aug 2026 18:16:03 +0330 Subject: [PATCH] Polish businesses list: EN owner name, new themes, overflow actions menu. Co-authored-by: Cursor --- .../src/utils/businessPrimaryColors.ts | 46 +++++- .../src/utils/businessPrimaryColors.ts | 46 +++++- .../BusinessActionsOverflowMenu.module.css | 75 +++++++++ .../BusinessActionsOverflowMenu.tsx | 156 ++++++++++++++++++ .../PrimaryColorSwatchControl.module.css | 3 + .../src/pages/BusinessesPage.module.css | 7 - apps/super-admin/src/pages/BusinessesPage.tsx | 47 +----- .../src/utils/businessPrimaryColors.ts | 46 +++++- 8 files changed, 377 insertions(+), 49 deletions(-) create mode 100644 apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css create mode 100644 apps/super-admin/src/components/BusinessActionsOverflowMenu.tsx diff --git a/apps/business/src/utils/businessPrimaryColors.ts b/apps/business/src/utils/businessPrimaryColors.ts index f2a22fc..da72ee5 100644 --- a/apps/business/src/utils/businessPrimaryColors.ts +++ b/apps/business/src/utils/businessPrimaryColors.ts @@ -6,6 +6,10 @@ export const BUSINESS_PRIMARY_COLOR_IDS = [ 'purple', 'light-blue', 'dark-blue', + 'brown', + 'navy', + 'dark-green', + 'orange', ] as const export type BusinessPrimaryColorId = (typeof BUSINESS_PRIMARY_COLOR_IDS)[number] @@ -106,7 +110,7 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record< ...CYAN_ACCENT, }, 'dark-blue': { - label: 'Dark Blue', + label: 'Blue', primary: '#3b82f6', primaryDark: '#2563eb', primaryLight: '#dbeafe', @@ -115,6 +119,46 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record< primaryDarkRgb: '37 99 235', ...CYAN_ACCENT, }, + brown: { + label: 'Brown', + primary: '#8b5e3c', + primaryDark: '#6f4a2f', + primaryLight: '#faf6f0', + primaryGlow: '#a67c52', + primaryRgb: '139 94 60', + primaryDarkRgb: '111 74 47', + ...CYAN_ACCENT, + }, + navy: { + label: 'Dark Blue', + primary: '#1e3a8a', + primaryDark: '#172554', + primaryLight: '#e0e7ff', + primaryGlow: '#1e40af', + primaryRgb: '30 58 138', + primaryDarkRgb: '23 37 84', + ...CYAN_ACCENT, + }, + 'dark-green': { + label: 'Dark Green', + primary: '#166534', + primaryDark: '#14532d', + primaryLight: '#dcfce7', + primaryGlow: '#15803d', + primaryRgb: '22 101 52', + primaryDarkRgb: '20 83 45', + ...CYAN_ACCENT, + }, + orange: { + label: 'Orange', + primary: '#ea580c', + primaryDark: '#c2410c', + primaryLight: '#ffedd5', + primaryGlow: '#f97316', + primaryRgb: '234 88 12', + primaryDarkRgb: '194 65 12', + ...PURPLE_ACCENT, + }, } export function normalizeBusinessPrimaryColorId(value: unknown): BusinessPrimaryColorId { diff --git a/apps/customer/src/utils/businessPrimaryColors.ts b/apps/customer/src/utils/businessPrimaryColors.ts index f2a22fc..da72ee5 100644 --- a/apps/customer/src/utils/businessPrimaryColors.ts +++ b/apps/customer/src/utils/businessPrimaryColors.ts @@ -6,6 +6,10 @@ export const BUSINESS_PRIMARY_COLOR_IDS = [ 'purple', 'light-blue', 'dark-blue', + 'brown', + 'navy', + 'dark-green', + 'orange', ] as const export type BusinessPrimaryColorId = (typeof BUSINESS_PRIMARY_COLOR_IDS)[number] @@ -106,7 +110,7 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record< ...CYAN_ACCENT, }, 'dark-blue': { - label: 'Dark Blue', + label: 'Blue', primary: '#3b82f6', primaryDark: '#2563eb', primaryLight: '#dbeafe', @@ -115,6 +119,46 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record< primaryDarkRgb: '37 99 235', ...CYAN_ACCENT, }, + brown: { + label: 'Brown', + primary: '#8b5e3c', + primaryDark: '#6f4a2f', + primaryLight: '#faf6f0', + primaryGlow: '#a67c52', + primaryRgb: '139 94 60', + primaryDarkRgb: '111 74 47', + ...CYAN_ACCENT, + }, + navy: { + label: 'Dark Blue', + primary: '#1e3a8a', + primaryDark: '#172554', + primaryLight: '#e0e7ff', + primaryGlow: '#1e40af', + primaryRgb: '30 58 138', + primaryDarkRgb: '23 37 84', + ...CYAN_ACCENT, + }, + 'dark-green': { + label: 'Dark Green', + primary: '#166534', + primaryDark: '#14532d', + primaryLight: '#dcfce7', + primaryGlow: '#15803d', + primaryRgb: '22 101 52', + primaryDarkRgb: '20 83 45', + ...CYAN_ACCENT, + }, + orange: { + label: 'Orange', + primary: '#ea580c', + primaryDark: '#c2410c', + primaryLight: '#ffedd5', + primaryGlow: '#f97316', + primaryRgb: '234 88 12', + primaryDarkRgb: '194 65 12', + ...PURPLE_ACCENT, + }, } export function normalizeBusinessPrimaryColorId(value: unknown): BusinessPrimaryColorId { diff --git a/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css b/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css new file mode 100644 index 0000000..e74ba24 --- /dev/null +++ b/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css @@ -0,0 +1,75 @@ +.wrap { + position: relative; + display: inline-flex; +} + +.trigger { + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + color: var(--text-secondary); + transition: background 0.2s, color 0.2s; +} + +.trigger:hover { + background: rgba(var(--primary-rgb) / 0.1); + color: var(--primary); +} + +.triggerOpen { + background: rgba(var(--primary-rgb) / 0.1); + color: var(--primary); +} + +.menu { + position: fixed; + z-index: 1200; + transform: translateX(-100%); + min-width: 168px; + 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); + box-shadow: var(--glass-shadow); + animation: menuIn 0.14s ease; +} + +.item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 9px 11px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + color: var(--text-primary); + text-align: start; + transition: background 0.15s, color 0.15s; +} + +.item:hover { + background: rgba(var(--primary-rgb) / 0.08); + color: var(--primary); +} + +.itemDanger:hover { + background: rgba(239, 68, 68, 0.08); + color: #ef4444; +} + +@keyframes menuIn { + from { + opacity: 0; + transform: translateX(-100%) translateY(-4px); + } + to { + opacity: 1; + transform: translateX(-100%) translateY(0); + } +} diff --git a/apps/super-admin/src/components/BusinessActionsOverflowMenu.tsx b/apps/super-admin/src/components/BusinessActionsOverflowMenu.tsx new file mode 100644 index 0000000..cf2a60e --- /dev/null +++ b/apps/super-admin/src/components/BusinessActionsOverflowMenu.tsx @@ -0,0 +1,156 @@ +import { useEffect, useRef, useState } from 'react' +import { createPortal } from 'react-dom' +import { Eraser, MoreHorizontal, Trash2, Users } from 'lucide-react' +import styles from './BusinessActionsOverflowMenu.module.css' + +type PopoverPosition = { + top: number + left: number +} + +interface BusinessActionsOverflowMenuProps { + businessName: string + onAllUsers: () => void + onWipeData: () => void + onRemove: () => void +} + +export function BusinessActionsOverflowMenu({ + businessName, + onAllUsers, + onWipeData, + onRemove, +}: BusinessActionsOverflowMenuProps) { + const [open, setOpen] = useState(false) + const [popoverPos, setPopoverPos] = useState(null) + const wrapRef = useRef(null) + const popoverRef = useRef(null) + const triggerRef = useRef(null) + + 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]) + + return ( +
+ + + {open && + popoverPos && + createPortal( +
+ + + +
, + document.body, + )} +
+ ) +} diff --git a/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css b/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css index af04ac1..9c6d1db 100644 --- a/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css +++ b/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css @@ -43,8 +43,11 @@ z-index: 1200; transform: translateX(-50%); display: flex; + flex-wrap: wrap; align-items: center; + justify-content: center; gap: 6px; + max-width: 280px; padding: 8px 10px; border-radius: var(--radius-sm); border: 1px solid var(--glass-border); diff --git a/apps/super-admin/src/pages/BusinessesPage.module.css b/apps/super-admin/src/pages/BusinessesPage.module.css index fa7c618..4cea5f3 100644 --- a/apps/super-admin/src/pages/BusinessesPage.module.css +++ b/apps/super-admin/src/pages/BusinessesPage.module.css @@ -462,13 +462,6 @@ line-height: 1.3; } -.ownerNameEn { - font-size: 12px; - font-weight: 500; - color: var(--text-secondary); - line-height: 1.3; -} - .ownerPhone { font-size: 11px; font-weight: 500; diff --git a/apps/super-admin/src/pages/BusinessesPage.tsx b/apps/super-admin/src/pages/BusinessesPage.tsx index 8c1f7d1..5a8fa48 100644 --- a/apps/super-admin/src/pages/BusinessesPage.tsx +++ b/apps/super-admin/src/pages/BusinessesPage.tsx @@ -11,11 +11,10 @@ import { Plus, RotateCcw, Search, - Trash2, UserCog, - Users, } from 'lucide-react' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' +import { BusinessActionsOverflowMenu } from '../components/BusinessActionsOverflowMenu' import { ToggleSwitch } from '../components/ToggleSwitch' import { formatCellForDisplay, toE164CellNumber } from '../lib/cellNumber' import type { @@ -962,12 +961,7 @@ export function BusinessesPage() {
-
- {b.ownerName ?? '—'} -
- {b.ownerNameEn ? ( -
{b.ownerNameEn}
- ) : null} +
{b.ownerNameEn || '—'}
{b.ownerCellNumber ? formatCellForDisplay(b.ownerCellNumber) : '—'}
@@ -1027,15 +1021,6 @@ export function BusinessesPage() { > - - - + goToBusinessUsers(b, 'all')} + onWipeData={() => openPurge(b)} + onRemove={() => setRemoveTarget(b)} + />
diff --git a/apps/super-admin/src/utils/businessPrimaryColors.ts b/apps/super-admin/src/utils/businessPrimaryColors.ts index f2a22fc..da72ee5 100644 --- a/apps/super-admin/src/utils/businessPrimaryColors.ts +++ b/apps/super-admin/src/utils/businessPrimaryColors.ts @@ -6,6 +6,10 @@ export const BUSINESS_PRIMARY_COLOR_IDS = [ 'purple', 'light-blue', 'dark-blue', + 'brown', + 'navy', + 'dark-green', + 'orange', ] as const export type BusinessPrimaryColorId = (typeof BUSINESS_PRIMARY_COLOR_IDS)[number] @@ -106,7 +110,7 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record< ...CYAN_ACCENT, }, 'dark-blue': { - label: 'Dark Blue', + label: 'Blue', primary: '#3b82f6', primaryDark: '#2563eb', primaryLight: '#dbeafe', @@ -115,6 +119,46 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record< primaryDarkRgb: '37 99 235', ...CYAN_ACCENT, }, + brown: { + label: 'Brown', + primary: '#8b5e3c', + primaryDark: '#6f4a2f', + primaryLight: '#faf6f0', + primaryGlow: '#a67c52', + primaryRgb: '139 94 60', + primaryDarkRgb: '111 74 47', + ...CYAN_ACCENT, + }, + navy: { + label: 'Dark Blue', + primary: '#1e3a8a', + primaryDark: '#172554', + primaryLight: '#e0e7ff', + primaryGlow: '#1e40af', + primaryRgb: '30 58 138', + primaryDarkRgb: '23 37 84', + ...CYAN_ACCENT, + }, + 'dark-green': { + label: 'Dark Green', + primary: '#166534', + primaryDark: '#14532d', + primaryLight: '#dcfce7', + primaryGlow: '#15803d', + primaryRgb: '22 101 52', + primaryDarkRgb: '20 83 45', + ...CYAN_ACCENT, + }, + orange: { + label: 'Orange', + primary: '#ea580c', + primaryDark: '#c2410c', + primaryLight: '#ffedd5', + primaryGlow: '#f97316', + primaryRgb: '234 88 12', + primaryDarkRgb: '194 65 12', + ...PURPLE_ACCENT, + }, } export function normalizeBusinessPrimaryColorId(value: unknown): BusinessPrimaryColorId {