Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions api/create-checkout-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,19 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {

const stripe = new Stripe(stripeSecretKey);

// Map planId to Stripe price IDs based on period
// Map planId to Stripe price IDs based on period (standardized naming)
const priceIdMap: Record<string, Record<string, string>> = {
'pro': {
'month': process.env.STRIPE_PRO_MONTHLY_PRICE_ID || 'price_pro_monthly',
'year': process.env.STRIPE_PRO_YEARLY_PRICE_ID || 'price_pro_yearly',
'month': process.env.STRIPE_PRICE_PRO_MONTH || process.env.STRIPE_PRO_MONTHLY_PRICE_ID || 'price_pro_monthly',
'year': process.env.STRIPE_PRICE_PRO_YEAR || process.env.STRIPE_PRO_YEARLY_PRICE_ID || 'price_pro_yearly',
},
'enterprise': {
'month': process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID || 'price_enterprise_monthly',
'year': process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID || 'price_enterprise_yearly',
'month': process.env.STRIPE_PRICE_ENTERPRISE_MONTH || process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID || 'price_enterprise_monthly',
'year': process.env.STRIPE_PRICE_ENTERPRISE_YEAR || process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID || 'price_enterprise_yearly',
},
'starter': {
'month': process.env.STRIPE_STARTER_MONTHLY_PRICE_ID || 'price_starter_monthly',
'year': process.env.STRIPE_STARTER_YEARLY_PRICE_ID || 'price_starter_yearly',
'month': process.env.STRIPE_PRICE_STARTER_MONTH || process.env.STRIPE_STARTER_MONTHLY_PRICE_ID || 'price_starter_monthly',
'year': process.env.STRIPE_PRICE_STARTER_YEAR || process.env.STRIPE_STARTER_YEARLY_PRICE_ID || 'price_starter_yearly',
},
};

Expand Down
14 changes: 7 additions & 7 deletions api/get-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,14 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const subscription = subscriptions.data[0];
const priceId = subscription.items.data[0]?.price.id;

// Map Stripe price IDs back to plan IDs
// Map Stripe price IDs back to plan IDs (standardized naming)
const planIdMap: Record<string, string> = {
[process.env.STRIPE_PRO_MONTHLY_PRICE_ID || 'price_pro_monthly']: 'pro',
[process.env.STRIPE_PRO_YEARLY_PRICE_ID || 'price_pro_yearly']: 'pro',
[process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID || 'price_enterprise_monthly']: 'enterprise',
[process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID || 'price_enterprise_yearly']: 'enterprise',
[process.env.STRIPE_STARTER_MONTHLY_PRICE_ID || 'price_starter_monthly']: 'starter',
[process.env.STRIPE_STARTER_YEARLY_PRICE_ID || 'price_starter_yearly']: 'starter',
[process.env.STRIPE_PRICE_PRO_MONTH || process.env.STRIPE_PRO_MONTHLY_PRICE_ID || 'price_pro_monthly']: 'pro',
[process.env.STRIPE_PRICE_PRO_YEAR || process.env.STRIPE_PRO_YEARLY_PRICE_ID || 'price_pro_yearly']: 'pro',
[process.env.STRIPE_PRICE_ENTERPRISE_MONTH || process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID || 'price_enterprise_monthly']: 'enterprise',
[process.env.STRIPE_PRICE_ENTERPRISE_YEAR || process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID || 'price_enterprise_yearly']: 'enterprise',
[process.env.STRIPE_PRICE_STARTER_MONTH || process.env.STRIPE_STARTER_MONTHLY_PRICE_ID || 'price_starter_monthly']: 'starter',
[process.env.STRIPE_PRICE_STARTER_YEAR || process.env.STRIPE_STARTER_YEARLY_PRICE_ID || 'price_starter_yearly']: 'starter',
};

const planId = planIdMap[priceId] || 'free';
Expand Down
14 changes: 7 additions & 7 deletions api/stripe-webhook.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
import Stripe from 'stripe';

// Helper function to map Stripe price IDs to plan types
// Helper function to map Stripe price IDs to plan types (standardized naming)
function mapPriceIdToPlanType(priceId: string): 'free' | 'pro' | 'enterprise' | 'starter' {
const priceIdMap: Record<string, 'free' | 'pro' | 'enterprise' | 'starter'> = {
[process.env.STRIPE_PRO_MONTHLY_PRICE_ID || 'price_pro_monthly']: 'pro',
[process.env.STRIPE_PRO_YEARLY_PRICE_ID || 'price_pro_yearly']: 'pro',
[process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID || 'price_enterprise_monthly']: 'enterprise',
[process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID || 'price_enterprise_yearly']: 'enterprise',
[process.env.STRIPE_STARTER_MONTHLY_PRICE_ID || 'price_starter_monthly']: 'starter',
[process.env.STRIPE_STARTER_YEARLY_PRICE_ID || 'price_starter_yearly']: 'starter',
[process.env.STRIPE_PRICE_PRO_MONTH || process.env.STRIPE_PRO_MONTHLY_PRICE_ID || 'price_pro_monthly']: 'pro',
[process.env.STRIPE_PRICE_PRO_YEAR || process.env.STRIPE_PRO_YEARLY_PRICE_ID || 'price_pro_yearly']: 'pro',
[process.env.STRIPE_PRICE_ENTERPRISE_MONTH || process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID || 'price_enterprise_monthly']: 'enterprise',
[process.env.STRIPE_PRICE_ENTERPRISE_YEAR || process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID || 'price_enterprise_yearly']: 'enterprise',
[process.env.STRIPE_PRICE_STARTER_MONTH || process.env.STRIPE_STARTER_MONTHLY_PRICE_ID || 'price_starter_monthly']: 'starter',
[process.env.STRIPE_PRICE_STARTER_YEAR || process.env.STRIPE_STARTER_YEARLY_PRICE_ID || 'price_starter_yearly']: 'starter',
};
return priceIdMap[priceId] || 'free';
}
Expand Down
14 changes: 12 additions & 2 deletions convex/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,18 @@ function mapSubscriptionStatus(stripeStatus: string): 'incomplete' | 'incomplete
}

function mapPriceIdToPlan(priceId: string): 'free' | 'pro' | 'enterprise' {
const pro = [process.env.STRIPE_PRICE_PRO_MONTH, process.env.STRIPE_PRICE_PRO_YEAR].filter(Boolean);
const enterprise = [process.env.STRIPE_PRICE_ENTERPRISE_MONTH, process.env.STRIPE_PRICE_ENTERPRISE_YEAR].filter(Boolean);
const pro = [
process.env.STRIPE_PRICE_PRO_MONTH,
process.env.STRIPE_PRICE_PRO_YEAR,
process.env.STRIPE_PRO_MONTHLY_PRICE_ID,
process.env.STRIPE_PRO_YEARLY_PRICE_ID
].filter(Boolean);
const enterprise = [
process.env.STRIPE_PRICE_ENTERPRISE_MONTH,
process.env.STRIPE_PRICE_ENTERPRISE_YEAR,
process.env.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID,
process.env.STRIPE_ENTERPRISE_YEARLY_PRICE_ID
].filter(Boolean);
Comment on lines +598 to +609
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Environment variable expansion looks good, but verify all new variables are configured

The extended support for multiple environment variable patterns improves configuration flexibility. However, ensure all new environment variables are properly configured in your deployment environments.


🏁 Script executed:

#!/bin/bash
# Check if these environment variables are documented in .env files or documentation

# Search for environment variable documentation
fd -t f -e md -e env -e example . | xargs rg -l "STRIPE_PRO_MONTHLY_PRICE_ID|STRIPE_PRO_YEARLY_PRICE_ID|STRIPE_ENTERPRISE_MONTHLY_PRICE_ID|STRIPE_ENTERPRISE_YEARLY_PRICE_ID"

# Check for .env example files
fd ".env" | xargs cat 2>/dev/null | grep -E "STRIPE_(PRO|ENTERPRISE)_(MONTHLY|YEARLY)_PRICE_ID"

# Look for usage in deployment configs
fd -t f -e yml -e yaml -e json . | xargs rg -l "STRIPE_PRO_MONTHLY_PRICE_ID|STRIPE_PRO_YEARLY_PRICE_ID"

Length of output: 365


Add missing Stripe environment variables to documentation and configuration
We couldn’t locate any of the eight new vars in your .env examples, markdown docs, or deployment configs. Please update your configuration templates and docs to include:

• STRIPE_PRICE_PRO_MONTH
• STRIPE_PRICE_PRO_YEAR
• STRIPE_PRO_MONTHLY_PRICE_ID
• STRIPE_PRO_YEARLY_PRICE_ID
• STRIPE_PRICE_ENTERPRISE_MONTH
• STRIPE_PRICE_ENTERPRISE_YEAR
• STRIPE_ENTERPRISE_MONTHLY_PRICE_ID
• STRIPE_ENTERPRISE_YEARLY_PRICE_ID

• Locations to update:

  • .env.example (or equivalent)
  • Project README or docs/configuration.md
  • CI/CD manifests, Kubernetes/Helm charts, or any deployment YAML/JSON files

Ensuring these are documented and set everywhere will prevent runtime misconfigurations.

🤖 Prompt for AI Agents
In convex/users.ts around lines 598 to 609, the code references eight new Stripe
env vars that are not present in project configuration or docs; add the
following variables to the project's configuration and documentation:
STRIPE_PRICE_PRO_MONTH, STRIPE_PRICE_PRO_YEAR, STRIPE_PRO_MONTHLY_PRICE_ID,
STRIPE_PRO_YEARLY_PRICE_ID, STRIPE_PRICE_ENTERPRISE_MONTH,
STRIPE_PRICE_ENTERPRISE_YEAR, STRIPE_ENTERPRISE_MONTHLY_PRICE_ID,
STRIPE_ENTERPRISE_YEARLY_PRICE_ID—update .env.example (with placeholder/example
values and whether each is required), README or docs/configuration.md (describe
purpose and expected format), and any CI/CD manifests, Kubernetes/Helm charts,
or deployment YAML/JSON (add as env vars/secrets or reference to secret store)
so deployments and developers have these variables defined and documented.


if (enterprise.includes(priceId)) return 'enterprise';
if (pro.includes(priceId)) return 'pro';
Expand Down
174 changes: 13 additions & 161 deletions src/components/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -977,167 +977,19 @@ const ChatInterface: React.FC = () => {
</DialogContent>
</Dialog>

<Dialog open={showWebsiteDialog} onOpenChange={setShowWebsiteDialog}>
<DialogTrigger asChild>
<Button
variant="outline"
size="lg"
className="bg-card/80 backdrop-blur-sm border-2 hover:border-primary/50 transition-all duration-200"
>
<Globe className="w-4 h-4 mr-2" />
Clone Website
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Globe className="w-5 h-5 text-primary" />
Website Analysis & Cloning
</DialogTitle>
</DialogHeader>
<div className="space-y-4 pt-4">
<div className="flex gap-2">
<Input
placeholder="Enter website URL to analyze (e.g., https://example.com)"
value={websiteUrl}
onChange={(e) => setWebsiteUrl(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleWebsiteAnalysis()}
className="flex-1"
/>
<Button onClick={handleWebsiteAnalysis} disabled={!websiteUrl.trim() || isAnalyzingWebsite}>
{isAnalyzingWebsite ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Globe className="w-4 h-4" />
)}
</Button>
</div>

{websiteAnalysis && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="space-y-4 max-h-96 overflow-y-auto"
>
<h4 className="font-medium text-sm">Website Analysis:</h4>
<div className="p-4 border rounded-lg bg-muted/30">
<div className="space-y-3 text-sm">
<div>
<span className="font-medium">URL:</span>
<a href={websiteAnalysis.url} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline ml-1">
{websiteAnalysis.url}
</a>
</div>
{websiteAnalysis.title && (
<div><span className="font-medium">Title:</span> {websiteAnalysis.title}</div>
)}
{websiteAnalysis.description && (
<div><span className="font-medium">Description:</span> {websiteAnalysis.description}</div>
)}
{websiteAnalysis.technologies && websiteAnalysis.technologies.length > 0 && (
<div>
<span className="font-medium">Technologies:</span>
<div className="flex flex-wrap gap-1 mt-1">
{websiteAnalysis.technologies.map((tech, index) => (
<Badge key={index} variant="secondary" className="text-xs">{tech}</Badge>
))}
</div>
</div>
)}
{websiteAnalysis.layout && (
<div><span className="font-medium">Layout:</span> {websiteAnalysis.layout}</div>
)}
{websiteAnalysis.components && websiteAnalysis.components.length > 0 && (
<div>
<span className="font-medium">Components:</span>
<div className="flex flex-wrap gap-1 mt-1">
{websiteAnalysis.components.map((comp, index) => (
<Badge key={index} variant="outline" className="text-xs">{comp}</Badge>
))}
</div>
</div>
)}
{websiteAnalysis.colorScheme && websiteAnalysis.colorScheme.length > 0 && (
<div>
<span className="font-medium">Colors:</span>
<div className="flex gap-1 mt-1">
{websiteAnalysis.colorScheme.slice(0, 8).map((color, index) => (
<div
key={index}
className="w-6 h-6 rounded border"
style={{ backgroundColor: color }}
title={color}
/>
))}
</div>
</div>
)}
</div>
<div className="grid grid-cols-2 gap-2 mt-4">
<Button
variant="secondary"
onClick={async () => {
setIsCrawling(true);
try {
const result = await crawlSite(websiteAnalysis.url, { maxPages: 12, includeSitemap: true });
setCrawlPages(result.pages.map(p => ({ url: p.url, title: p.title })));
toast.success(`Crawled ${result.pages.length} pages with Firecrawl`);
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Firecrawl failed');
} finally {
setIsCrawling(false);
}
}}
disabled={isCrawling}
>
{isCrawling ? <Loader2 className="w-4 h-4 animate-spin mr-2"/> : <Globe className="w-4 h-4 mr-2"/>}
Crawl with Firecrawl
</Button>
<Button
className="w-full"
onClick={() => {
handleWebsiteAnalysis();
setShowWebsiteDialog(false);
}}
>
<Plus className="w-4 h-4 mr-2" />
Add to Chat
</Button>
</div>
{crawlPages.length > 0 && (
<div className="mt-3">
<h5 className="font-medium text-sm mb-2">Crawled Pages</h5>
<div className="max-h-40 overflow-auto space-y-1 text-xs">
{crawlPages.slice(0, 20).map((p, i) => (
<div key={i} className="flex items-center justify-between gap-2">
<a href={p.url} target="_blank" rel="noopener noreferrer" className="text-primary truncate">
{p.title || p.url}
</a>
<a href={p.url} target="_blank" rel="noopener noreferrer" className="text-muted-foreground">
<ExternalLink className="w-3 h-3" />
</a>
</div>
))}
</div>
</div>
)}
<Button
className="w-full mt-4"
onClick={() => {
const summary = `Crawled ${crawlPages.length} pages from ${websiteAnalysis.url}. Key pages:\n` + crawlPages.slice(0,5).map(p=>`- ${p.title || p.url} (${p.url})`).join('\n');
setInput(prev => prev + (prev ? '\n\n' : '') + summary + '\n\nUse these references to recreate the UI.');
setShowWebsiteDialog(false);
}}
>
<Plus className="w-4 h-4 mr-2" />
Insert Crawl Summary
</Button>
</div>
</motion.div>
)}
</div>
</DialogContent>
</Dialog>
{/* Clone website feature simplified - button now directly adds prompt to chat */}
<Button
variant="outline"
size="lg"
className="bg-card/80 backdrop-blur-sm border-2 hover:border-primary/50 transition-all duration-200"
onClick={() => {
setInput(prev => prev + (prev ? '\n\n' : '') + 'Clone this website URL: ');
toast.success('Clone prompt added to chat!');
}}
>
<Globe className="w-4 h-4 mr-2" />
Clone Website
</Button>
</motion.div>

{/* Feature highlights */}
Expand Down
Loading