diff --git a/app/api/analyses/[id]/run/route.ts b/app/api/analyses/[id]/run/route.ts index 3f2f632..844339d 100644 --- a/app/api/analyses/[id]/run/route.ts +++ b/app/api/analyses/[id]/run/route.ts @@ -1,4 +1,4 @@ -import { NextRequest } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import { aiConfigErrorMessage, getAnthropicClient, isAiConfigured } from '@/lib/ai-gateway' import { z } from 'zod' import { getCurrentAccessToken, getCurrentUser } from '@/lib/auth' @@ -132,11 +132,27 @@ const CODE_EXTENSIONS = new Set([ ]) export async function POST( - request: NextRequest, + req: NextRequest, { params }: { params: Promise<{ id: string }> } ) { + // Await params in Next.js 16 const { id } = await params + // Check authentication first - redirect if not authenticated + const accessToken = await getCurrentAccessToken().catch(() => null) + if (!accessToken) { + const redirectUrl = new URL('/api/auth/github/login', req.url) + redirectUrl.searchParams.set('returnTo', `/dashboard/analyses/${id}`) + return NextResponse.redirect(redirectUrl) + } + + const user = await getCurrentUser().catch(() => null) + if (!user) { + const redirectUrl = new URL('/api/auth/github/login', req.url) + redirectUrl.searchParams.set('returnTo', `/dashboard/analyses/${id}`) + return NextResponse.redirect(redirectUrl) + } + // Create a stream for progress updates const encoder = new TextEncoder() const stream = new ReadableStream({ @@ -146,25 +162,12 @@ export async function POST( } try { - const accessToken = await getCurrentAccessToken() - if (!accessToken) { - send({ error: 'Sign in with GitHub before running an analysis.' }) - controller.close() - return - } if (!isAiConfigured()) { send({ error: aiConfigErrorMessage() }) controller.close() return } - const user = await getCurrentUser() - if (!user) { - send({ error: 'Sign in with GitHub before running an analysis.' }) - controller.close() - return - } - let sub = await getSubscriptionByGithubId(user.github_id).catch(() => null) if (!sub) { sub = await upsertSubscription({ github_id: user.github_id }).catch(() => null) diff --git a/app/api/checkout/route.ts b/app/api/checkout/route.ts index 1dfc543..539e909 100644 --- a/app/api/checkout/route.ts +++ b/app/api/checkout/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { getCurrentUser } from '@/lib/auth' -import { getAppUrl, getProPriceId, getStripe, isStripeConfigured } from '@/lib/stripe' +import { getAppUrl, getPriceId, getStripe, isStripeConfigured } from '@/lib/stripe' import { updateUserBilling } from '@/lib/queries' export async function GET(request: NextRequest) { @@ -40,7 +40,7 @@ export async function GET(request: NextRequest) { customer: customerId, line_items: [ { - price: getProPriceId(), + price: getPriceId(), quantity: 1, }, ], diff --git a/app/api/stripe/webhook/route.ts b/app/api/stripe/webhook/route.ts index f2bacbe..f8d1aab 100644 --- a/app/api/stripe/webhook/route.ts +++ b/app/api/stripe/webhook/route.ts @@ -1,16 +1,8 @@ import { NextRequest, NextResponse } from 'next/server' -import { getStripe, getWebhookSecret } from '@/lib/stripe' -import { upsertSubscription, getSubscriptionByStripeCustomerId, getUserByGithubId } from '@/lib/queries' +import { getStripeWebhookSecret, getStripe, getPriceIdForPlan } from '@/lib/stripe' +import { upsertSubscription, getSubscriptionByStripeCustomerId, getUserByGithubId, updateUserBilling } from '@/lib/queries' import { grantCredits, CREDITS } from '@/lib/credits' import type Stripe from 'stripe' -import { CREDITS, grantCredits } from '@/lib/credits' -import { - getSubscriptionByStripeCustomerId, - getUserByGithubId, - updateUserBilling, - upsertSubscription, -} from '@/lib/queries' -import { getPriceIdForPlan, getStripe } from '@/lib/stripe' export const dynamic = 'force-dynamic' export const runtime = 'nodejs' @@ -136,13 +128,6 @@ export async function POST(request: NextRequest) { const webhookSecret = getWebhookSecret() if (!signature || !webhookSecret) { return NextResponse.json({ error: 'Webhook not configured' }, { status: 400 }) - if (!process.env.STRIPE_WEBHOOK_SECRET || !process.env.STRIPE_SECRET_KEY) { - console.error('[stripe/webhook] Missing STRIPE_WEBHOOK_SECRET or STRIPE_SECRET_KEY in environment') - return NextResponse.json({ error: 'Webhook not configured' }, { status: 503 }) - } - - if (!signature) { - return NextResponse.json({ error: 'Missing stripe-signature header' }, { status: 400 }) } let body: string diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index c5c8818..d609863 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -1,5 +1,5 @@ import { getCurrentUser } from '@/lib/auth' -import { DashboardHeader } from '@/components/dashboard-header' +import { DashboardSidebar } from '@/components/dashboard-sidebar' export default async function DashboardLayout({ children, @@ -14,10 +14,19 @@ export default async function DashboardLayout({ } return ( -
- -
- {children} +
+ {/* Sidebar */} + + + {/* Main Content */} +
+
+ {children} +
) diff --git a/components/dashboard-sidebar.tsx b/components/dashboard-sidebar.tsx new file mode 100644 index 0000000..ad54b40 --- /dev/null +++ b/components/dashboard-sidebar.tsx @@ -0,0 +1,211 @@ +'use client' + +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import { + Zap, LayoutDashboard, FolderGit2, Lightbulb, Bot, + BarChart3, Clock, GitPullRequest, Settings, Plug, + CreditCard, LogOut +} from 'lucide-react' +import { cn } from '@/lib/utils' + +interface SidebarProps { + user?: { + name?: string + email?: string + plan?: 'free' | 'pro' | 'scale' + } +} + +export function DashboardSidebar({ user }: SidebarProps) { + const pathname = usePathname() + + const isActive = (href: string) => pathname === href + + return ( + + ) +} diff --git a/lib/stripe.ts b/lib/stripe.ts index 6bf5f63..d7db3ba 100644 --- a/lib/stripe.ts +++ b/lib/stripe.ts @@ -4,45 +4,11 @@ let stripeInstance: Stripe | null = null type StripeMode = 'live' | 'test' -function normalizeMode(value?: string): StripeMode { - if (value?.toLowerCase() === 'test') return 'test' - return 'live' -} - function detectModeFromKey(key: string): StripeMode { if (key.startsWith('sk_test_') || key.startsWith('rk_test_')) return 'test' return 'live' } -function getConfiguredMode(): StripeMode { - return normalizeMode(process.env.STRIPE_MODE) -} - -function getSecretKeyForMode(mode: StripeMode): string { - if (mode === 'live') { - return process.env.STRIPE_SECRET_KEY_LIVE || process.env.STRIPE_SECRET_KEY || '' - } - return process.env.STRIPE_SECRET_KEY_TEST || process.env.STRIPE_SECRET_KEY || '' -} - -export function getWebhookSecret(): string { - const mode = getConfiguredMode() - if (mode === 'live') { - return process.env.STRIPE_WEBHOOK_SECRET_LIVE || process.env.STRIPE_WEBHOOK_SECRET || '' - } - return process.env.STRIPE_WEBHOOK_SECRET_TEST || process.env.STRIPE_WEBHOOK_SECRET || '' -} - -export function isStripeConfigured(): boolean { - return !!(getSecretKeyForMode(getConfiguredMode()) && getPriceId()) -} - -export function getStripe(): Stripe { - const mode = getConfiguredMode() - const secretKey = getSecretKeyForMode(mode) - - if (!secretKey) { - throw new Error('STRIPE_SECRET_KEY is not set') function getLiveStripeEnv(name: 'SECRET_KEY' | 'WEBHOOK_SECRET' | 'PRO_PRICE_ID' | 'SCALE_PRICE_ID'): string { return process.env[`STRIPE_LIVE_${name}`] || process.env[`STRIPE_${name}`] || '' } @@ -65,13 +31,7 @@ export function getStripe(): Stripe { console.log('[v0] Available env keys containing STRIPE:', Object.keys(process.env).filter(k => k.includes('STRIPE'))) if (!secretKey) { - throw new Error('STRIPE_LIVE_SECRET_KEY is not set') - } - const keyMode = detectModeFromKey(secretKey) - if (keyMode !== mode) { - throw new Error( - `Stripe mode mismatch: STRIPE_MODE=${mode} but key appears to be ${keyMode}. Update keys to match selected mode.`, - ) + throw new Error('STRIPE_SECRET_KEY is not set') } if (!stripeInstance) { stripeInstance = new Stripe(secretKey, { @@ -137,22 +97,9 @@ export function isPaidPlan(plan: string | null | undefined): plan is Exclude /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.