fix: Multiple Command Center improvements
LISTINGS PAGE: - Added missing Sparkles import PORTFOLIO PAGE: - Changed dropdown menu to open downward (top-full mt-1) instead of upward for better visibility SEO PAGE: - Added cleanDomain() helper to sanitize input - Removes whitespace, protocol, www, and trailing slashes - Fixes 'hushen. app' -> 'hushen.app' input issue PRICING PAGE: - Removed accent highlight from 'TLDs Tracked' StatCard All StatCards now have consistent styling without green accent highlights.
This commit is contained in:
@ -22,6 +22,7 @@ import {
|
|||||||
X,
|
X,
|
||||||
Tag,
|
Tag,
|
||||||
Store,
|
Store,
|
||||||
|
Sparkles,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|||||||
@ -446,8 +446,8 @@ export default function PortfolioPage() {
|
|||||||
className="fixed inset-0 z-40"
|
className="fixed inset-0 z-40"
|
||||||
onClick={() => setOpenMenuId(null)}
|
onClick={() => setOpenMenuId(null)}
|
||||||
/>
|
/>
|
||||||
{/* Menu - opens upward */}
|
{/* Menu - opens downward */}
|
||||||
<div className="absolute right-0 bottom-full mb-1 z-50 w-48 py-1 bg-background-secondary border border-border/50 rounded-xl shadow-xl">
|
<div className="absolute right-0 top-full mt-1 z-50 w-48 py-1 bg-background-secondary border border-border/50 rounded-xl shadow-xl">
|
||||||
<button
|
<button
|
||||||
onClick={() => { handleHealthCheck(domain.domain); setOpenMenuId(null) }}
|
onClick={() => { handleHealthCheck(domain.domain); setOpenMenuId(null) }}
|
||||||
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 transition-colors"
|
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 transition-colors"
|
||||||
|
|||||||
@ -315,7 +315,7 @@ export default function TLDPricingPage() {
|
|||||||
<PageContainer>
|
<PageContainer>
|
||||||
{/* Stats Overview */}
|
{/* Stats Overview */}
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<StatCard title="TLDs Tracked" value={total > 0 ? total.toLocaleString() : '—'} subtitle="updated daily" icon={Globe} accent />
|
<StatCard title="TLDs Tracked" value={total > 0 ? total.toLocaleString() : '—'} subtitle="updated daily" icon={Globe} />
|
||||||
<StatCard title="Lowest Price" value={total > 0 ? `$${stats.lowestPrice.toFixed(2)}` : '—'} icon={DollarSign} />
|
<StatCard title="Lowest Price" value={total > 0 ? `$${stats.lowestPrice.toFixed(2)}` : '—'} icon={DollarSign} />
|
||||||
<StatCard title="Hottest TLD" value={total > 0 ? `.${stats.hottestTld}` : '—'} subtitle="rising prices" icon={TrendingUp} />
|
<StatCard title="Hottest TLD" value={total > 0 ? `.${stats.hottestTld}` : '—'} subtitle="rising prices" icon={TrendingUp} />
|
||||||
<StatCard title="Renewal Traps" value={stats.trapCount.toString()} subtitle="high renewal ratio" icon={AlertTriangle} />
|
<StatCard title="Renewal Traps" value={stats.trapCount.toString()} subtitle="high renewal ratio" icon={AlertTriangle} />
|
||||||
|
|||||||
@ -83,18 +83,29 @@ export default function SEOPage() {
|
|||||||
localStorage.setItem('seo-recent-searches', JSON.stringify(updated))
|
localStorage.setItem('seo-recent-searches', JSON.stringify(updated))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cleanDomain = (d: string): string => {
|
||||||
|
// Remove whitespace, protocol, www, and trailing slashes
|
||||||
|
return d.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, '')
|
||||||
|
.replace(/^https?:\/\//, '')
|
||||||
|
.replace(/^www\./, '')
|
||||||
|
.replace(/\/.*$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
const handleSearch = async (e: React.FormEvent) => {
|
const handleSearch = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!domain.trim()) return
|
const cleanedDomain = cleanDomain(domain)
|
||||||
|
if (!cleanedDomain) return
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
setSeoData(null)
|
setSeoData(null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await api.request<SEOData>(`/seo/${encodeURIComponent(domain.trim())}`)
|
const data = await api.request<SEOData>(`/seo/${encodeURIComponent(cleanedDomain)}`)
|
||||||
setSeoData(data)
|
setSeoData(data)
|
||||||
saveRecentSearch(domain.trim().toLowerCase())
|
saveRecentSearch(cleanedDomain)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message || 'Failed to analyze domain')
|
setError(err.message || 'Failed to analyze domain')
|
||||||
} finally {
|
} finally {
|
||||||
@ -103,13 +114,14 @@ export default function SEOPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleQuickSearch = async (searchDomain: string) => {
|
const handleQuickSearch = async (searchDomain: string) => {
|
||||||
setDomain(searchDomain)
|
const cleanedDomain = cleanDomain(searchDomain)
|
||||||
|
setDomain(cleanedDomain)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
setSeoData(null)
|
setSeoData(null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await api.request<SEOData>(`/seo/${encodeURIComponent(searchDomain)}`)
|
const data = await api.request<SEOData>(`/seo/${encodeURIComponent(cleanedDomain)}`)
|
||||||
setSeoData(data)
|
setSeoData(data)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message || 'Failed to analyze domain')
|
setError(err.message || 'Failed to analyze domain')
|
||||||
|
|||||||
Reference in New Issue
Block a user