feat: Make Auctions and Intelligence pages publicly accessible
CHANGES: - Auctions page now uses public layout (Header/Footer) instead of CommandCenterLayout - Intelligence page now uses public layout (Header/Footer) instead of CommandCenterLayout - Both pages accessible without login - Login CTA banner shown to non-authenticated users - Opportunities tab locked for non-authenticated users (shows ?) - Price alerts feature requires login - Consistent layout between both public pages: - Same hero section with title and refresh button - Same 4-column stats grid - Same CTA banner design - Same filter/search layout - Same table component - Same pagination design
This commit is contained in:
166
analysis_3.md
Normal file
166
analysis_3.md
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
Um die Churn Rate (Absprungrate) zu senken und den Umsatz pro Kunde (LTV - Lifetime Value) zu steigern, musst du das Mindset des Nutzers ändern:
|
||||||
|
|
||||||
|
**Von:** *"Ich nutze Pounce, um eine Domain zu **finden**."* (Einmaliges Projekt)
|
||||||
|
**Zu:** *"Ich nutze Pounce, um mein Domain-Business zu **betreiben**."* (Laufender Prozess)
|
||||||
|
|
||||||
|
Wenn Pounce nur ein "Such-Tool" ist, kündigen die Leute, sobald sie fündig wurden. Wenn Pounce aber ihr "Betriebssystem" wird, bleiben sie für immer.
|
||||||
|
|
||||||
|
Hier sind 4 Strategien, um Pounce unverzichtbar zu machen:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. Strategie: Vom "Jäger" zum "Wächter" (Portfolio Monitoring)
|
||||||
|
*Ziel: Den Nutzer binden, auch wenn er gerade nichts kaufen will.*
|
||||||
|
|
||||||
|
Viele Domainer und Agenturen besitzen bereits 50-500 Domains. Sie haben Angst, eine Verlängerung zu verpassen oder technische Fehler nicht zu bemerken.
|
||||||
|
|
||||||
|
* **Das Feature:** **"My Portfolio Health"**
|
||||||
|
Der Nutzer importiert seine *eigenen* Domains in Pounce (nicht um sie zu kaufen, sondern zu verwalten).
|
||||||
|
* **Uptime Monitor:** Ist meine Seite noch online?
|
||||||
|
* **SSL Monitor:** Läuft mein Zertifikat ab?
|
||||||
|
* **Expiration Alert:** Erinnere mich 30 Tage vor Ablauf (besser als die Spam-Mails der Registrare).
|
||||||
|
* **Blacklist Check:** Landet meine Domain auf einer Spam-Liste?
|
||||||
|
|
||||||
|
* **Der Lock-in Effekt:**
|
||||||
|
Niemand kündigt das Tool, das seine Assets überwacht ("Versicherungs-Psychologie"). Wenn du ihre 50 Domains überwachst, bist du unverzichtbar.
|
||||||
|
|
||||||
|
### 2. Strategie: Der "Micro-Marktplatz" (Liquidity)
|
||||||
|
*Ziel: Mehr Umsatz durch Transaktionen.*
|
||||||
|
|
||||||
|
Wenn ein "Hunter" eine Domain über Pounce findet, will er sie oft später wieder verkaufen (Flipping). Aktuell schickst du ihn dafür weg zu Sedo. Warum nicht im Haus behalten?
|
||||||
|
|
||||||
|
* **Das Feature:** **"Pounce 'For Sale' Landing Pages"**
|
||||||
|
Ein User (Trader/Tycoon) kann für seine Domains mit einem Klick eine schicke Verkaufsseite erstellen.
|
||||||
|
* *Domain:* `super-startup.ai`
|
||||||
|
* *Pounce generiert:* `pounce.ch/buy/super-startup-ai`
|
||||||
|
* *Design:* Hochwertig, zeigt deine "Valuation Daten" (Pounce Score) an, um den Preis zu rechtfertigen.
|
||||||
|
* *Kontakt:* Ein einfaches Kontaktformular, das die Anfrage direkt an den User leitet.
|
||||||
|
|
||||||
|
* **Das Geld:**
|
||||||
|
* Entweder Teil des Abo-Preises ("Erstelle 5 Verkaufsseiten kostenlos").
|
||||||
|
* Oder: Du nimmst keine Provision, aber der Käufer muss sich bei Pounce registrieren, um den Verkäufer zu kontaktieren (Lead Gen).
|
||||||
|
|
||||||
|
### 3. Strategie: SEO-Daten & Backlinks (Neue Zielgruppe)
|
||||||
|
*Ziel: Kunden mit hohem Budget gewinnen (Agenturen).*
|
||||||
|
|
||||||
|
SEO-Agenturen kündigen fast nie, weil sie monatliche Budgets für Tools haben. Sie suchen Domains nicht wegen dem Namen, sondern wegen der **Power** (Backlinks).
|
||||||
|
|
||||||
|
* **Das Feature:** **"SEO Juice Detector"**
|
||||||
|
Wenn eine Domain droppt, prüfst du nicht nur den Namen, sondern (über günstige APIs wie Moz oder durch Scraping öffentlicher Daten), ob Backlinks existieren.
|
||||||
|
* *Anzeige:* "Domain `alte-bäckerei-münchen.de` ist frei. Hat Links von `sueddeutsche.de` und `wikipedia.org`."
|
||||||
|
* **Der Wert:** Solche Domains sind für SEOs 100€ - 500€ wert, auch wenn der Name hässlich ist.
|
||||||
|
* **Monetarisierung:** Das ist ein reines **Tycoon-Feature ($29 oder sogar $49/Monat)**.
|
||||||
|
|
||||||
|
### 4. Strategie: Alerts "nach Maß" (Hyper-Personalisierung)
|
||||||
|
*Ziel: Den Nutzer täglich zurückholen.*
|
||||||
|
|
||||||
|
Wenn ich nur eine Mail bekomme "Hier sind 100 neue Domains", ist das oft Spam für mich. Ich will nur *genau das*, was ich suche.
|
||||||
|
|
||||||
|
* **Das Feature:** **"Sniper Alerts"**
|
||||||
|
Der User kann extrem spezifische Filter speichern:
|
||||||
|
* *"Informiere mich NUR, wenn eine 4-Letter .com Domain droppt, die kein 'q' oder 'x' enthält."*
|
||||||
|
* *"Informiere mich, wenn eine .ch Domain droppt, die das Wort 'Immo' enthält."*
|
||||||
|
* **Der Effekt:** Wenn die SMS/Mail kommt, weiß der User: "Das ist relevant". Er klickt, loggt sich ein, bleibt aktiv.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Zusammenfassung des erweiterten Business-Modells
|
||||||
|
|
||||||
|
So sieht deine Umsatz-Maschine dann aus:
|
||||||
|
|
||||||
|
| Stufe | Was der User tut | Warum er bleibt (Retention) | Dein Umsatz |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **Phase 1: Finding** | Sucht freie/droppende Domains. | Findet bessere Deals durch Spam-Filter. | $9 / Monat |
|
||||||
|
| **Phase 2: Monitoring** | Überwacht Wettbewerber & eigene Domains. | Angst, Status-Änderungen zu verpassen (Versicherung). | Churn sinkt drastisch. |
|
||||||
|
| **Phase 3: Selling** | Erstellt Verkaufs-Landings via Pounce. | Nutzt Pounce als Schaufenster für sein Business. | User ist "locked in". |
|
||||||
|
| **Phase 4: SEO** | Sucht Backlink-Monster. | Verdient Geld mit deinen Daten (ROI). | $29 - $49 / Monat |
|
||||||
|
|
||||||
|
### Mein Tipp für den Start:
|
||||||
|
Konzentriere dich auf **Strategie 1 (Portfolio Monitoring)** als erstes Zusatz-Feature nach dem Launch.
|
||||||
|
|
||||||
|
Warum?
|
||||||
|
Es ist technisch einfach (du hast die Ping-Skripte ja schon für die Analyse gebaut). Du erlaubst dem User einfach, Domains *manuell* hinzuzufügen.
|
||||||
|
Sobald ein User mal 50 seiner eigenen Domains eingetragen hat, wird er sein Abo **niemals kündigen**, weil er sonst seine Überwachung verliert. Das ist der ultimative "Golden Handcuff".
|
||||||
|
|
||||||
|
Vertrauen ist im Domain-Business tatsächlich die **härteste Währung**. Die Branche ist leider voll von Betrügern (Domain-Diebstahl, Phishing, Fake-Auktionen).
|
||||||
|
|
||||||
|
Wenn `pounce.ch` als "Command Center" wahrgenommen werden soll, muss die Plattform **sauberer sein als der Rest**.
|
||||||
|
|
||||||
|
Hier ist ein **4-Säulen-Sicherheitskonzept**, mit dem du Missbrauch verhinderst und gleichzeitig massives Vertrauen bei deinen echten Nutzern aufbaust.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Säule 1: Identity Verification (Wer bist du?)
|
||||||
|
*Hürde: Betrüger hassen Identifikation.*
|
||||||
|
|
||||||
|
Du darfst "Tycoon"-Features (und vor allem Verkaufs-Features) nicht einfach jedem geben, der eine E-Mail-Adresse hat.
|
||||||
|
|
||||||
|
1. **Stripe Identity / Radar:**
|
||||||
|
Nutze für die Zahlungsabwicklung Stripe. Stripe hat eingebaute Betrugserkennung ("Radar"). Wenn jemand eine gestohlene Kreditkarte nutzt, blockiert Stripe ihn meist sofort. Das ist deine erste Firewall.
|
||||||
|
2. **SMS-Verifizierung (2FA):**
|
||||||
|
Jeder Account, der Domains verkaufen oder überwachen will, muss eine **Handynummer verifizieren**. Wegwerf-Nummern (VoIP) werden blockiert. Das erhöht die Hürde für Spammer massiv.
|
||||||
|
3. **LinkedIn-Login (Optional für Trust):**
|
||||||
|
Biete an: "Verbinde dein LinkedIn für den 'Verified Professional' Status". Ein Profil mit 500+ Kontakten und Historie ist selten ein Fake.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Säule 2: Asset Verification (Gehört dir das wirklich?)
|
||||||
|
*Hürde: Verhindern, dass Leute fremde Domains als ihre eigenen ausgeben.*
|
||||||
|
|
||||||
|
Das ist der wichtigste Punkt, wenn du Features wie "Portfolio Monitoring" oder "For Sale Pages" anbietest.
|
||||||
|
|
||||||
|
**Die technische Lösung: DNS Ownership Verify**
|
||||||
|
Bevor ein Nutzer eine Domain in sein Portfolio aufnehmen kann, um sie zu verkaufen oder tief zu analysieren, muss er beweisen, dass er der Admin ist.
|
||||||
|
* **Wie es funktioniert:**
|
||||||
|
1. User fügt `mein-startup.ch` hinzu.
|
||||||
|
2. Pounce sagt: "Bitte erstelle einen TXT-Record in deinen DNS-Einstellungen mit dem Inhalt: `pounce-verification=847392`."
|
||||||
|
3. Dein System prüft den Record.
|
||||||
|
4. Nur wenn er da ist -> **Domain Verified ✅**.
|
||||||
|
|
||||||
|
*Das ist der Industriestandard (macht Google auch). Wer keinen Zugriff auf die DNS hat, kann die Domain nicht claimen.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Säule 3: Content Monitoring (Was machst du damit?)
|
||||||
|
*Hürde: Verhindern, dass deine "For Sale"-Seiten für Phishing genutzt werden.*
|
||||||
|
|
||||||
|
Wenn User über Pounce Verkaufsseiten ("Landers") erstellen können, könnten sie dort versuchen, Bankdaten abzugreifen.
|
||||||
|
|
||||||
|
1. **Automatischer Blacklist-Scan:**
|
||||||
|
Jede Domain, die ins System kommt, wird sofort gegen **Google Safe Browsing** und **Spamhaus** geprüft. Ist die Domain dort als "Malware" gelistet? -> **Sofortiger Ban.**
|
||||||
|
2. **Keyword-Blocking:**
|
||||||
|
Erlaube keine Titel oder Texte auf Verkaufsseiten, die Wörter enthalten wie: "Login", "Bank", "Verify", "Paypal", "Password".
|
||||||
|
3. **No Custom HTML:**
|
||||||
|
Erlaube Usern auf ihren Verkaufsseiten *kein* eigenes HTML/JavaScript. Nur Text und vordefinierte Buttons. So können sie keine Schadsoftware einschleusen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Säule 4: The "Safe Harbor" Badge (Marketing)
|
||||||
|
*Nutzen: Du machst die Sicherheit zu deinem Verkaufsargument.*
|
||||||
|
|
||||||
|
Du kommunizierst diese Strenge nicht als "Nervigkeit", sondern als **Qualitätsmerkmal**.
|
||||||
|
|
||||||
|
* **Das "Pounce Verified" Siegel:**
|
||||||
|
Auf jeder Verkaufsseite oder in jedem Profil zeigst du an:
|
||||||
|
* ✅ **ID Verified** (Handy/Zahlung geprüft)
|
||||||
|
* ✅ **Owner Verified** (DNS geprüft)
|
||||||
|
* ✅ **Clean History** (Keine Spam-Reports)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Prozess bei Verstößen ("Zero Tolerance")
|
||||||
|
|
||||||
|
Du brauchst klare AGBs ("Terms of Service"):
|
||||||
|
1. **One Strike Policy:** Wer versucht, Phishing zu betreiben oder gestohlene Domains anzubieten, wird sofort permanent gesperrt. Keine Diskussion.
|
||||||
|
2. **Reporting Button:** Gib der Community Macht. Ein "Report Abuse"-Button auf jeder Seite. Wenn 2-3 unabhängige User etwas melden, wird das Asset automatisch offline genommen, bis du es geprüft hast.
|
||||||
|
|
||||||
|
### Zusammenfassung: Der "Trust Stack"
|
||||||
|
|
||||||
|
| Ebene | Maßnahme | Effekt |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Login** | SMS / 2FA + Stripe Radar | Hält Bots und Kreditkartenbetrüger fern. |
|
||||||
|
| **Portfolio** | **DNS TXT Record (Zwingend)** | Nur der echte Besitzer kann Domains verwalten. |
|
||||||
|
| **Marktplatz** | Google Safe Browsing Check | Verhindert Malware/Phishing auf deiner Plattform. |
|
||||||
|
| **Frontend** | "Verified Owner" Badge | Käufer wissen: Das hier ist sicher. |
|
||||||
|
|
||||||
|
**Damit positionierst du Pounce als den "Safe Space" im wilden Westen des Domain-Handels.** Das ist für seriöse Investoren oft wichtiger als der Preis.
|
||||||
@ -3,7 +3,8 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useStore } from '@/lib/store'
|
import { useStore } from '@/lib/store'
|
||||||
import { api } from '@/lib/api'
|
import { api } from '@/lib/api'
|
||||||
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
|
import { Header } from '@/components/Header'
|
||||||
|
import { Footer } from '@/components/Footer'
|
||||||
import { PremiumTable, Badge, PlatformBadge, StatCard, PageContainer } from '@/components/PremiumTable'
|
import { PremiumTable, Badge, PlatformBadge, StatCard, PageContainer } from '@/components/PremiumTable'
|
||||||
import {
|
import {
|
||||||
Clock,
|
Clock,
|
||||||
@ -12,15 +13,13 @@ import {
|
|||||||
Flame,
|
Flame,
|
||||||
Timer,
|
Timer,
|
||||||
Gavel,
|
Gavel,
|
||||||
ChevronUp,
|
|
||||||
ChevronDown,
|
|
||||||
ChevronsUpDown,
|
|
||||||
DollarSign,
|
DollarSign,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Target,
|
Target,
|
||||||
X,
|
X,
|
||||||
TrendingUp,
|
Lock,
|
||||||
Loader2,
|
Sparkles,
|
||||||
|
ArrowRight,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@ -66,7 +65,7 @@ const PLATFORMS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export default function AuctionsPage() {
|
export default function AuctionsPage() {
|
||||||
const { isAuthenticated, subscription } = useStore()
|
const { isAuthenticated } = useStore()
|
||||||
|
|
||||||
const [allAuctions, setAllAuctions] = useState<Auction[]>([])
|
const [allAuctions, setAllAuctions] = useState<Auction[]>([])
|
||||||
const [endingSoon, setEndingSoon] = useState<Auction[]>([])
|
const [endingSoon, setEndingSoon] = useState<Auction[]>([])
|
||||||
@ -202,43 +201,91 @@ export default function AuctionsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandCenterLayout
|
<div className="min-h-screen bg-background flex flex-col">
|
||||||
title="Auctions"
|
<Header />
|
||||||
subtitle={getSubtitle()}
|
|
||||||
actions={
|
{/* Hero Section */}
|
||||||
|
<section className="relative pt-24 pb-12 overflow-hidden">
|
||||||
|
{/* Background Effects */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div className="absolute top-[-20%] left-1/2 -translate-x-1/2 w-[1200px] h-[800px] bg-accent/[0.03] rounded-full blur-[120px]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-display tracking-tight text-foreground">
|
||||||
|
Domain Auctions
|
||||||
|
</h1>
|
||||||
|
<p className="text-foreground-muted mt-2">{getSubtitle()}</p>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
className="flex items-center gap-2 px-4 py-2 text-sm text-foreground-muted hover:text-foreground
|
className="flex items-center gap-2 px-4 py-2.5 text-sm text-foreground-muted hover:text-foreground
|
||||||
hover:bg-foreground/5 rounded-lg transition-all disabled:opacity-50"
|
bg-foreground/5 hover:bg-foreground/10 border border-border/50 rounded-xl
|
||||||
|
transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
||||||
<span className="hidden sm:inline">Refresh</span>
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
}
|
</div>
|
||||||
>
|
|
||||||
<PageContainer>
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||||
<StatCard title="All Auctions" value={allAuctions.length} icon={Gavel} />
|
<StatCard title="All Auctions" value={allAuctions.length} icon={Gavel} />
|
||||||
<StatCard title="Ending Soon" value={endingSoon.length} icon={Timer} accent />
|
<StatCard title="Ending Soon" value={endingSoon.length} icon={Timer} accent />
|
||||||
<StatCard title="Hot Auctions" value={hotAuctions.length} subtitle="20+ bids" icon={Flame} />
|
<StatCard title="Hot Auctions" value={hotAuctions.length} subtitle="20+ bids" icon={Flame} />
|
||||||
<StatCard title="Opportunities" value={opportunities.length} icon={Target} />
|
<StatCard
|
||||||
|
title="Opportunities"
|
||||||
|
value={isAuthenticated ? opportunities.length : '—'}
|
||||||
|
subtitle={isAuthenticated ? undefined : 'Login required'}
|
||||||
|
icon={Target}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Login Banner for Opportunities */}
|
||||||
|
{!isAuthenticated && (
|
||||||
|
<div className="mb-8 p-5 bg-gradient-to-r from-accent/10 via-accent/5 to-transparent border border-accent/20 rounded-2xl">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 bg-accent/20 rounded-xl flex items-center justify-center">
|
||||||
|
<Sparkles className="w-6 h-6 text-accent" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-foreground">Unlock Smart Opportunities</h3>
|
||||||
|
<p className="text-sm text-foreground-muted">Get AI-powered auction analysis and personalized recommendations</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="flex items-center justify-center gap-2 px-6 py-3 bg-gradient-to-r from-accent to-accent/80
|
||||||
|
text-background rounded-xl font-medium hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all"
|
||||||
|
>
|
||||||
|
Sign In <ArrowRight className="w-4 h-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex flex-wrap items-center gap-2 p-1.5 bg-background-secondary/30 border border-border/30 rounded-2xl w-fit">
|
<div className="flex flex-wrap items-center gap-2 p-1.5 bg-background-secondary/30 border border-border/30 rounded-2xl w-fit mb-6">
|
||||||
{[
|
{[
|
||||||
{ id: 'all' as const, label: 'All', icon: Gavel, count: allAuctions.length },
|
{ id: 'all' as const, label: 'All', icon: Gavel, count: allAuctions.length },
|
||||||
{ id: 'ending' as const, label: 'Ending Soon', icon: Timer, count: endingSoon.length, color: 'warning' },
|
{ id: 'ending' as const, label: 'Ending Soon', icon: Timer, count: endingSoon.length, color: 'warning' },
|
||||||
{ id: 'hot' as const, label: 'Hot', icon: Flame, count: hotAuctions.length },
|
{ id: 'hot' as const, label: 'Hot', icon: Flame, count: hotAuctions.length },
|
||||||
{ id: 'opportunities' as const, label: 'Opportunities', icon: Target, count: opportunities.length },
|
{ id: 'opportunities' as const, label: 'Opportunities', icon: Target, count: opportunities.length, locked: !isAuthenticated },
|
||||||
].map((tab) => (
|
].map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => {
|
||||||
|
if (tab.locked) return
|
||||||
|
setActiveTab(tab.id)
|
||||||
|
}}
|
||||||
|
disabled={tab.locked}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex items-center gap-2 px-4 py-2.5 text-sm font-medium rounded-xl transition-all",
|
"flex items-center gap-2 px-4 py-2.5 text-sm font-medium rounded-xl transition-all",
|
||||||
|
tab.locked && "opacity-50 cursor-not-allowed",
|
||||||
activeTab === tab.id
|
activeTab === tab.id
|
||||||
? tab.color === 'warning'
|
? tab.color === 'warning'
|
||||||
? "bg-amber-500 text-background"
|
? "bg-amber-500 text-background"
|
||||||
@ -246,18 +293,18 @@ export default function AuctionsPage() {
|
|||||||
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<tab.icon className="w-4 h-4" />
|
{tab.locked ? <Lock className="w-4 h-4" /> : <tab.icon className="w-4 h-4" />}
|
||||||
<span className="hidden sm:inline">{tab.label}</span>
|
<span className="hidden sm:inline">{tab.label}</span>
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
"text-xs px-1.5 py-0.5 rounded",
|
"text-xs px-1.5 py-0.5 rounded tabular-nums",
|
||||||
activeTab === tab.id ? "bg-background/20" : "bg-foreground/10"
|
activeTab === tab.id ? "bg-background/20" : "bg-foreground/10"
|
||||||
)}>{tab.count}</span>
|
)}>{tab.locked ? '?' : tab.count}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3 mb-6">
|
||||||
<div className="relative flex-1 min-w-[200px] max-w-md">
|
<div className="relative flex-1 min-w-[200px] max-w-md">
|
||||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-subtle" />
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-subtle" />
|
||||||
<input
|
<input
|
||||||
@ -352,7 +399,7 @@ export default function AuctionsPage() {
|
|||||||
align: 'right',
|
align: 'right',
|
||||||
render: (a) => (
|
render: (a) => (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-foreground">{formatCurrency(a.current_bid)}</span>
|
<span className="font-medium text-foreground tabular-nums">{formatCurrency(a.current_bid)}</span>
|
||||||
{a.buy_now_price && (
|
{a.buy_now_price && (
|
||||||
<p className="text-xs text-accent">Buy: {formatCurrency(a.buy_now_price)}</p>
|
<p className="text-xs text-accent">Buy: {formatCurrency(a.buy_now_price)}</p>
|
||||||
)}
|
)}
|
||||||
@ -367,7 +414,7 @@ export default function AuctionsPage() {
|
|||||||
hideOnMobile: true,
|
hideOnMobile: true,
|
||||||
render: (a) => (
|
render: (a) => (
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
"font-medium flex items-center justify-end gap-1",
|
"font-medium flex items-center justify-end gap-1 tabular-nums",
|
||||||
a.num_bids >= 20 ? "text-accent" : a.num_bids >= 10 ? "text-amber-400" : "text-foreground-muted"
|
a.num_bids >= 20 ? "text-accent" : a.num_bids >= 10 ? "text-amber-400" : "text-foreground-muted"
|
||||||
)}>
|
)}>
|
||||||
{a.num_bids}
|
{a.num_bids}
|
||||||
@ -419,7 +466,11 @@ export default function AuctionsPage() {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</PageContainer>
|
</div>
|
||||||
</CommandCenterLayout>
|
</section>
|
||||||
|
|
||||||
|
<div className="flex-1" />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useStore } from '@/lib/store'
|
import { useStore } from '@/lib/store'
|
||||||
import { api } from '@/lib/api'
|
import { api } from '@/lib/api'
|
||||||
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
|
import { Header } from '@/components/Header'
|
||||||
|
import { Footer } from '@/components/Footer'
|
||||||
import { PremiumTable, Badge, StatCard, PageContainer } from '@/components/PremiumTable'
|
import { PremiumTable, Badge, StatCard, PageContainer } from '@/components/PremiumTable'
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
@ -18,6 +19,8 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
Bell,
|
Bell,
|
||||||
X,
|
X,
|
||||||
|
Sparkles,
|
||||||
|
ArrowRight,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
@ -34,7 +37,7 @@ interface TLDData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function IntelligencePage() {
|
export default function IntelligencePage() {
|
||||||
const { subscription } = useStore()
|
const { isAuthenticated } = useStore()
|
||||||
|
|
||||||
const [tldData, setTldData] = useState<TLDData[]>([])
|
const [tldData, setTldData] = useState<TLDData[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@ -96,24 +99,38 @@ export default function IntelligencePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandCenterLayout
|
<div className="min-h-screen bg-background flex flex-col">
|
||||||
title="TLD Intelligence"
|
<Header />
|
||||||
subtitle={getSubtitle()}
|
|
||||||
actions={
|
{/* Hero Section */}
|
||||||
|
<section className="relative pt-24 pb-12 overflow-hidden">
|
||||||
|
{/* Background Effects */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div className="absolute top-[-20%] left-1/2 -translate-x-1/2 w-[1200px] h-[800px] bg-accent/[0.03] rounded-full blur-[120px]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-display tracking-tight text-foreground">
|
||||||
|
TLD Intelligence
|
||||||
|
</h1>
|
||||||
|
<p className="text-foreground-muted mt-2">{getSubtitle()}</p>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
className="flex items-center gap-2 px-4 py-2 text-sm text-foreground-muted hover:text-foreground
|
className="flex items-center gap-2 px-4 py-2.5 text-sm text-foreground-muted hover:text-foreground
|
||||||
hover:bg-foreground/5 rounded-lg transition-all disabled:opacity-50"
|
bg-foreground/5 hover:bg-foreground/10 border border-border/50 rounded-xl
|
||||||
|
transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
||||||
<span className="hidden sm:inline">Refresh</span>
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
}
|
</div>
|
||||||
>
|
|
||||||
<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 mb-8">
|
||||||
<StatCard
|
<StatCard
|
||||||
title="TLDs Tracked"
|
title="TLDs Tracked"
|
||||||
value={total > 0 ? total.toLocaleString() : '—'}
|
value={total > 0 ? total.toLocaleString() : '—'}
|
||||||
@ -140,8 +157,32 @@ export default function IntelligencePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* CTA Banner for non-authenticated users */}
|
||||||
|
{!isAuthenticated && (
|
||||||
|
<div className="mb-8 p-5 bg-gradient-to-r from-accent/10 via-accent/5 to-transparent border border-accent/20 rounded-2xl">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-12 h-12 bg-accent/20 rounded-xl flex items-center justify-center">
|
||||||
|
<Bell className="w-6 h-6 text-accent" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-foreground">Set Price Alerts</h3>
|
||||||
|
<p className="text-sm text-foreground-muted">Get notified when TLD prices drop to your target</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="flex items-center justify-center gap-2 px-6 py-3 bg-gradient-to-r from-accent to-accent/80
|
||||||
|
text-background rounded-xl font-medium hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all"
|
||||||
|
>
|
||||||
|
Sign In <ArrowRight className="w-4 h-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
<div className="flex flex-col sm:flex-row gap-3 mb-6">
|
||||||
<div className="relative flex-1 max-w-md">
|
<div className="relative flex-1 max-w-md">
|
||||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted" />
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted" />
|
||||||
<input
|
<input
|
||||||
@ -255,7 +296,7 @@ export default function IntelligencePage() {
|
|||||||
href={`/tld-pricing/${tld.tld}`}
|
href={`/tld-pricing/${tld.tld}`}
|
||||||
className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
|
className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
title="Set price alert"
|
title="View details"
|
||||||
>
|
>
|
||||||
<Bell className="w-4 h-4" />
|
<Bell className="w-4 h-4" />
|
||||||
</Link>
|
</Link>
|
||||||
@ -268,7 +309,7 @@ export default function IntelligencePage() {
|
|||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{total > 50 && (
|
{total > 50 && (
|
||||||
<div className="flex items-center justify-center gap-4 pt-2">
|
<div className="flex items-center justify-center gap-4 pt-6">
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage(Math.max(0, page - 1))}
|
onClick={() => setPage(Math.max(0, page - 1))}
|
||||||
disabled={page === 0}
|
disabled={page === 0}
|
||||||
@ -292,7 +333,11 @@ export default function IntelligencePage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</PageContainer>
|
</div>
|
||||||
</CommandCenterLayout>
|
</section>
|
||||||
|
|
||||||
|
<div className="flex-1" />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
0
frontend/src/app/watchlist/page.tsx
Normal file → Executable file
0
frontend/src/app/watchlist/page.tsx
Normal file → Executable file
Reference in New Issue
Block a user