Add logo aspect ratios and businesses renewal sort with low-SMS alert.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
e020c74fb1
commit
c24f6c06f7
@@ -58,6 +58,43 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.lowSmsAlert {
|
||||
margin: 12px 0 0;
|
||||
padding: 10px 14px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
border: 1px solid rgba(239, 68, 68, 0.28);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.lowSmsAlertLabel {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lowSmsAlertLink {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lowSmsAlertLink:hover {
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.lowSmsAlertBalance {
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -440,6 +477,13 @@
|
||||
transition: transform 0.15s, box-shadow 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btnPrimary {
|
||||
color: white;
|
||||
background: linear-gradient(135deg, var(--primary-glow) 0%, var(--primary) 55%, var(--primary-dark) 100%);
|
||||
|
||||
@@ -333,6 +333,7 @@ export function BusinessesPage() {
|
||||
const [draftName, setDraftName] = useState('')
|
||||
const [draftDomain, setDraftDomain] = useState('')
|
||||
const [draftCategory, setDraftCategory] = useState('')
|
||||
const [draftSort, setDraftSort] = useState<'created' | 'renewal'>('created')
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [editBusiness, setEditBusiness] = useState<BusinessListItem | null>(null)
|
||||
@@ -484,7 +485,7 @@ export function BusinessesPage() {
|
||||
return () => {
|
||||
controller.abort()
|
||||
}
|
||||
}, [page, pageSize, appliedFilters.name, appliedFilters.domain, appliedFilters.category])
|
||||
}, [page, pageSize, appliedFilters.name, appliedFilters.domain, appliedFilters.category, appliedFilters.sort])
|
||||
|
||||
const totalPages = useMemo(() => {
|
||||
const total = data?.total ?? 0
|
||||
@@ -509,6 +510,7 @@ export function BusinessesPage() {
|
||||
...(draftName.trim() ? { name: draftName.trim() } : {}),
|
||||
...(draftDomain.trim() ? { domain: draftDomain.trim() } : {}),
|
||||
...(draftCategory.trim() ? { category: draftCategory.trim() } : {}),
|
||||
...(draftSort !== 'created' ? { sort: draftSort } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -516,6 +518,7 @@ export function BusinessesPage() {
|
||||
setDraftName('')
|
||||
setDraftDomain('')
|
||||
setDraftCategory('')
|
||||
setDraftSort('created')
|
||||
setPage(1)
|
||||
setAppliedFilters({})
|
||||
}
|
||||
@@ -1503,6 +1506,32 @@ export function BusinessesPage() {
|
||||
aria-label="Category"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.field}>
|
||||
<select
|
||||
id="filter-sort"
|
||||
value={draftSort}
|
||||
onChange={(e) => {
|
||||
const sort = e.target.value === 'renewal' ? 'renewal' : 'created'
|
||||
setDraftSort(sort)
|
||||
setPage(1)
|
||||
setAppliedFilters((prev) => {
|
||||
const next = { ...prev }
|
||||
if (sort === 'created') {
|
||||
delete next.sort
|
||||
} else {
|
||||
next.sort = sort
|
||||
}
|
||||
return next
|
||||
})
|
||||
}}
|
||||
aria-label="Sort by"
|
||||
title="Sort by"
|
||||
>
|
||||
<option value="created">Date created</option>
|
||||
<option value="renewal">Nearest renewal</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.filterActions}>
|
||||
@@ -1529,6 +1558,43 @@ export function BusinessesPage() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{data?.lowSmsBusinesses && data.lowSmsBusinesses.length > 0 && (
|
||||
<div className={styles.lowSmsAlert} role="status">
|
||||
<span className={styles.lowSmsAlertLabel}>
|
||||
Low SMS credits (< {data.lowSmsThreshold ?? 100}):
|
||||
</span>{' '}
|
||||
{data.lowSmsBusinesses.map((b, i) => (
|
||||
<span key={String(b.id)}>
|
||||
{i > 0 ? ', ' : ''}
|
||||
<button
|
||||
type="button"
|
||||
className={styles.lowSmsAlertLink}
|
||||
onClick={() => {
|
||||
const match = data.items.find((item) => String(item.id) === String(b.id))
|
||||
if (match) {
|
||||
openSmsSettings(match)
|
||||
return
|
||||
}
|
||||
setDraftName(b.name)
|
||||
setPage(1)
|
||||
setAppliedFilters((prev) => ({
|
||||
...prev,
|
||||
name: b.name,
|
||||
}))
|
||||
}}
|
||||
>
|
||||
{b.name}
|
||||
{b.domain ? ` (${b.domain})` : ''}
|
||||
</button>
|
||||
<span className={styles.lowSmsAlertBalance}>
|
||||
{' '}
|
||||
· {b.smsBalance.toLocaleString('en-US')}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.tablePanel}>
|
||||
<div className={styles.tableWrap}>
|
||||
<div className={styles.tableHeader}>
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface ListBusinessesParams {
|
||||
name?: string
|
||||
domain?: string
|
||||
category?: string
|
||||
/** Default: created (newest first). renewal = nearest next/overdue renewal. */
|
||||
sort?: 'created' | 'renewal'
|
||||
}
|
||||
|
||||
export interface UpdateBusinessPayload {
|
||||
@@ -61,6 +63,7 @@ export async function listBusinesses(params: ListBusinessesParams, signal?: Abor
|
||||
if (params.name) q.set('name', params.name)
|
||||
if (params.domain) q.set('domain', params.domain)
|
||||
if (params.category) q.set('category', params.category)
|
||||
if (params.sort && params.sort !== 'created') q.set('sort', params.sort)
|
||||
|
||||
const query = q.toString()
|
||||
const path = `/businesses${query ? `?${query}` : ''}`
|
||||
|
||||
@@ -137,5 +137,13 @@ export interface BusinessesListResponse {
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
/** Businesses with prepaid SMS credits below lowSmsThreshold (all pages). */
|
||||
lowSmsBusinesses?: Array<{
|
||||
id: string
|
||||
name: string
|
||||
domain: string | null
|
||||
smsBalance: number
|
||||
}>
|
||||
lowSmsThreshold?: number
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user