Add customer/manager access UI and tighten customers filters.

Business and super-admin can set Customer vs Manager roles (Admin only for super-admin), and the customers page drops clutter while filtering by all/customers/managers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 21:31:28 +03:30
co-authored by Cursor
parent 404c008ced
commit f622e6d605
28 changed files with 1280 additions and 241 deletions
@@ -3,13 +3,14 @@
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background: var(--glass-bg);
backdrop-filter: blur(var(--blur-glass));
-webkit-backdrop-filter: blur(var(--blur-glass));
border-bottom: 1px solid var(--glass-border);
position: sticky;
top: 0;
z-index: 50;
background: color-mix(in srgb, #ffffff 42%, transparent);
backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
-webkit-backdrop-filter: saturate(180%) blur(var(--blur-glass, 28px));
border-bottom: 1px solid var(--glass-border);
transform: translateZ(0);
}
.left {
@@ -73,3 +73,11 @@
.editor p:last-child {
margin-bottom: 0;
}
.editor blockquote {
margin: 0.35em 0;
margin-inline-start: 1.4em;
margin-inline-end: 0;
padding: 0;
border: 0;
}
@@ -1,7 +1,18 @@
import { useCallback, useEffect, useRef } from 'react'
import { Bold, Italic, List, ListOrdered, Underline } from 'lucide-react'
import {
Bold,
IndentDecrease,
IndentIncrease,
Italic,
List,
ListOrdered,
Underline,
} from 'lucide-react'
import styles from './RichTextEditor.module.css'
const INDENT_STEP_PX = 24
const INDENT_BLOCK_TAGS = new Set(['P', 'DIV', 'LI', 'BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4'])
interface RichTextEditorProps {
value: string
onChange: (value: string) => void
@@ -36,6 +47,51 @@ export function RichTextEditor({
syncChange()
}
function findIndentBlock(editor: HTMLElement): HTMLElement | null {
const selection = window.getSelection()
if (!selection || selection.rangeCount === 0) return null
let node: Node | null = selection.anchorNode
while (node && node !== editor) {
if (node instanceof HTMLElement && INDENT_BLOCK_TAGS.has(node.tagName)) {
return node
}
node = node.parentNode
}
return null
}
function changeIndent(direction: 1 | -1) {
const editor = editorRef.current
if (!editor) return
editor.focus()
const block = findIndentBlock(editor)
if (!block) {
document.execCommand(direction > 0 ? 'indent' : 'outdent')
syncChange()
return
}
const current =
Number.parseFloat(block.style.paddingInlineStart) ||
Number.parseFloat(getComputedStyle(block).paddingInlineStart) ||
0
const next = Math.max(0, Math.round(current + direction * INDENT_STEP_PX))
if (next === 0) {
block.style.paddingInlineStart = ''
} else {
block.style.paddingInlineStart = `${next}px`
}
syncChange()
}
function onEditorKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey) return
e.preventDefault()
changeIndent(e.shiftKey ? -1 : 1)
}
return (
<div className={styles.wrapper}>
<div className={styles.toolbar}>
@@ -70,6 +126,23 @@ export function RichTextEditor({
>
<ListOrdered size={14} />
</button>
<span className={styles.divider} />
<button
type="button"
onClick={() => changeIndent(-1)}
title="Decrease indent"
aria-label="Decrease indent"
>
<IndentDecrease size={14} />
</button>
<button
type="button"
onClick={() => changeIndent(1)}
title="Increase indent"
aria-label="Increase indent"
>
<IndentIncrease size={14} />
</button>
</div>
<div
ref={editorRef}
@@ -80,6 +153,7 @@ export function RichTextEditor({
aria-multiline="true"
data-placeholder={placeholder}
onInput={syncChange}
onKeyDown={onEditorKeyDown}
suppressContentEditableWarning
/>
</div>