✅ 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),
|
||||
):
|
||||
"""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
|
||||
existing = await db.execute(
|
||||
select(PortfolioDomain).where(
|
||||
|
||||
@ -77,9 +77,9 @@ const comparisonFeatures = [
|
||||
{ name: 'Watchlist Domains', scout: '5', trader: '50', tycoon: '500' },
|
||||
{ name: 'Check Frequency', scout: 'Daily', trader: 'Hourly', tycoon: '10 min' },
|
||||
{ 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: 'Expiry Tracking', scout: '—', trader: '✓', tycoon: '✓' },
|
||||
{ name: 'Expiry Tracking', scout: '—', trader: 'check', tycoon: 'check' },
|
||||
]
|
||||
|
||||
const faqs = [
|
||||
@ -230,10 +230,7 @@ export default function PricingPage() {
|
||||
<ul className="space-y-3 mb-8">
|
||||
{tier.features.map((feature) => (
|
||||
<li key={feature.text} className="flex items-start gap-3">
|
||||
<Check className={clsx(
|
||||
"w-4 h-4 mt-0.5 shrink-0",
|
||||
feature.highlight ? "text-accent" : "text-foreground-muted"
|
||||
)} strokeWidth={2.5} />
|
||||
<Check className="w-4 h-4 mt-0.5 shrink-0 text-accent" strokeWidth={2.5} />
|
||||
<span className={clsx(
|
||||
"text-body-sm",
|
||||
feature.highlight ? "text-foreground" : "text-foreground-muted"
|
||||
@ -286,9 +283,21 @@ export default function PricingPage() {
|
||||
{comparisonFeatures.map((feature) => (
|
||||
<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-center text-body-sm text-foreground-muted">{feature.scout}</td>
|
||||
<td className="py-4 px-4 text-center text-body-sm text-foreground">{feature.trader}</td>
|
||||
<td className="py-4 px-4 text-center text-body-sm text-foreground">{feature.tycoon}</td>
|
||||
<td className="py-4 px-4 text-center text-body-sm text-foreground-muted">
|
||||
{feature.scout === 'check' ? (
|
||||
<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>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user