✅ Enforce tier limits & green checkmarks on pricing
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Backend: - Add portfolio limit enforcement (Scout: 0, Trader: 25, Tycoon: unlimited) - Return proper error messages when limits exceeded Frontend (Pricing): - All checkmarks now green (text-accent) - Comparison table uses Check icons instead of ✓ text - Consistent visual styling across all tiers
This commit is contained in:
@ -262,6 +262,36 @@ async def add_portfolio_domain(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Add a domain to portfolio."""
|
"""Add a domain to portfolio."""
|
||||||
|
from app.models.subscription import Subscription, SubscriptionTier, TIER_CONFIG
|
||||||
|
|
||||||
|
# Check subscription portfolio limit
|
||||||
|
await db.refresh(current_user, ["subscription"])
|
||||||
|
|
||||||
|
if current_user.subscription:
|
||||||
|
portfolio_limit = current_user.subscription.portfolio_limit
|
||||||
|
else:
|
||||||
|
portfolio_limit = TIER_CONFIG[SubscriptionTier.SCOUT].get("portfolio_limit", 0)
|
||||||
|
|
||||||
|
# Count current portfolio domains
|
||||||
|
count_result = await db.execute(
|
||||||
|
select(func.count(PortfolioDomain.id)).where(
|
||||||
|
PortfolioDomain.user_id == current_user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
current_count = count_result.scalar() or 0
|
||||||
|
|
||||||
|
# Check limit (-1 means unlimited)
|
||||||
|
if portfolio_limit != -1 and current_count >= portfolio_limit:
|
||||||
|
if portfolio_limit == 0:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Portfolio feature not available on Scout plan. Upgrade to Trader or Tycoon.",
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=f"Portfolio limit reached ({portfolio_limit} domains). Upgrade to add more.",
|
||||||
|
)
|
||||||
|
|
||||||
# Check if domain already exists in user's portfolio
|
# Check if domain already exists in user's portfolio
|
||||||
existing = await db.execute(
|
existing = await db.execute(
|
||||||
select(PortfolioDomain).where(
|
select(PortfolioDomain).where(
|
||||||
|
|||||||
@ -77,9 +77,9 @@ const comparisonFeatures = [
|
|||||||
{ name: 'Watchlist Domains', scout: '5', trader: '50', tycoon: '500' },
|
{ name: 'Watchlist Domains', scout: '5', trader: '50', tycoon: '500' },
|
||||||
{ name: 'Check Frequency', scout: 'Daily', trader: 'Hourly', tycoon: '10 min' },
|
{ name: 'Check Frequency', scout: 'Daily', trader: 'Hourly', tycoon: '10 min' },
|
||||||
{ name: 'Portfolio Domains', scout: '—', trader: '25', tycoon: 'Unlimited' },
|
{ name: 'Portfolio Domains', scout: '—', trader: '25', tycoon: 'Unlimited' },
|
||||||
{ name: 'Domain Valuation', scout: '—', trader: '✓', tycoon: '✓' },
|
{ name: 'Domain Valuation', scout: '—', trader: 'check', tycoon: 'check' },
|
||||||
{ name: 'Price History', scout: '—', trader: '90 days', tycoon: 'Unlimited' },
|
{ name: 'Price History', scout: '—', trader: '90 days', tycoon: 'Unlimited' },
|
||||||
{ name: 'Expiry Tracking', scout: '—', trader: '✓', tycoon: '✓' },
|
{ name: 'Expiry Tracking', scout: '—', trader: 'check', tycoon: 'check' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const faqs = [
|
const faqs = [
|
||||||
@ -230,10 +230,7 @@ export default function PricingPage() {
|
|||||||
<ul className="space-y-3 mb-8">
|
<ul className="space-y-3 mb-8">
|
||||||
{tier.features.map((feature) => (
|
{tier.features.map((feature) => (
|
||||||
<li key={feature.text} className="flex items-start gap-3">
|
<li key={feature.text} className="flex items-start gap-3">
|
||||||
<Check className={clsx(
|
<Check className="w-4 h-4 mt-0.5 shrink-0 text-accent" strokeWidth={2.5} />
|
||||||
"w-4 h-4 mt-0.5 shrink-0",
|
|
||||||
feature.highlight ? "text-accent" : "text-foreground-muted"
|
|
||||||
)} strokeWidth={2.5} />
|
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
"text-body-sm",
|
"text-body-sm",
|
||||||
feature.highlight ? "text-foreground" : "text-foreground-muted"
|
feature.highlight ? "text-foreground" : "text-foreground-muted"
|
||||||
@ -286,9 +283,21 @@ export default function PricingPage() {
|
|||||||
{comparisonFeatures.map((feature) => (
|
{comparisonFeatures.map((feature) => (
|
||||||
<tr key={feature.name} className="border-b border-border/50">
|
<tr key={feature.name} className="border-b border-border/50">
|
||||||
<td className="py-4 px-4 text-body-sm text-foreground">{feature.name}</td>
|
<td className="py-4 px-4 text-body-sm text-foreground">{feature.name}</td>
|
||||||
<td className="py-4 px-4 text-center text-body-sm text-foreground-muted">{feature.scout}</td>
|
<td className="py-4 px-4 text-center text-body-sm text-foreground-muted">
|
||||||
<td className="py-4 px-4 text-center text-body-sm text-foreground">{feature.trader}</td>
|
{feature.scout === 'check' ? (
|
||||||
<td className="py-4 px-4 text-center text-body-sm text-foreground">{feature.tycoon}</td>
|
<Check className="w-5 h-5 text-accent mx-auto" strokeWidth={2.5} />
|
||||||
|
) : feature.scout}
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4 text-center text-body-sm text-foreground">
|
||||||
|
{feature.trader === 'check' ? (
|
||||||
|
<Check className="w-5 h-5 text-accent mx-auto" strokeWidth={2.5} />
|
||||||
|
) : feature.trader}
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4 text-center text-body-sm text-foreground">
|
||||||
|
{feature.tycoon === 'check' ? (
|
||||||
|
<Check className="w-5 h-5 text-accent mx-auto" strokeWidth={2.5} />
|
||||||
|
) : feature.tycoon}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user