Skip to content

Commit 06e29c2

Browse files
author
Gerome El-assaad
committed
feat: Add new features and update existing functionality
1 parent 71a47a1 commit 06e29c2

21 files changed

Lines changed: 674 additions & 226 deletions

AGENTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Agent Guidelines for CodingIT
2+
3+
## Build/Lint/Test Commands
4+
- **Development**: `npm run dev` (Next.js with Turbo)
5+
- **Build**: `npm run build` (Next.js production build)
6+
- **Start**: `npm run start` (Next.js production server)
7+
- **Lint**: `npm run lint` (ESLint with Next.js rules)
8+
- **No test framework configured** - run lint after changes
9+
10+
## Code Style Guidelines
11+
12+
### Formatting
13+
- **Quotes**: Single quotes only (`'string'`)
14+
- **Semicolons**: Never use semicolons
15+
- **Imports**: Auto-sorted with `@trivago/prettier-plugin-sort-imports`
16+
17+
### TypeScript
18+
- **Strict mode**: Enabled in `tsconfig.json`
19+
- **Path aliases**: Use `@/*` for relative imports from root
20+
- **Types**: Always use explicit types, prefer interfaces for objects
21+
22+
### Naming Conventions
23+
- **Variables/Functions**: camelCase (`userName`, `getUserData`)
24+
- **Components**: PascalCase (`UserProfile`, `ChatInput`)
25+
- **Files**: kebab-case for components (`user-profile.tsx`), camelCase for utilities (`authUtils.ts`)
26+
27+
### Error Handling
28+
- Use `console.warn()` for non-critical warnings
29+
- Handle async errors with `.catch()` or try-catch
30+
- Validate inputs with TypeScript types and runtime checks
31+
32+
### Imports
33+
- Group imports: React/Next, third-party libraries, local utilities
34+
- Use absolute imports with `@/` alias
35+
- Sort imports automatically (handled by Prettier plugin)
36+
37+
### Best Practices
38+
- No comments unless absolutely necessary
39+
- Use functional components with hooks
40+
- Follow React/Next.js conventions
41+
- Maintain consistent file structure
Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,60 @@
1-
import { NextRequest, NextResponse } from 'next/server'
1+
import { NextResponse } from 'next/server'
22
import { createServerClient } from '@/lib/supabase-server'
3-
import { getTeamUsageLimits, getTeamSubscription } from '@/lib/subscription'
3+
import { stripe } from '@/lib/stripe'
44

5-
export const dynamic = 'force-dynamic'
5+
export async function GET() {
6+
const supabase = createServerClient()
7+
const {
8+
data: { user }
9+
} = await supabase.auth.getUser()
610

7-
export async function GET(request: NextRequest) {
8-
try {
9-
const supabase = createServerClient()
10-
const { data: { user }, error: authError } = await supabase.auth.getUser()
11+
if (!user) {
12+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
13+
}
1114

12-
if (authError || !user?.id) {
13-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
14-
}
15+
if (!stripe) {
16+
return NextResponse.json({ error: 'Stripe not configured' }, { status: 500 })
17+
}
1518

16-
// Get user's default team
17-
const { data: userTeam, error: teamError } = await supabase
18-
.from('users_teams')
19-
.select('teams (id)')
20-
.eq('user_id', user.id)
21-
.eq('is_default', true)
22-
.single()
19+
const { data: teamData } = await supabase
20+
.from('users_teams')
21+
.select('teams (stripe_customer_id)')
22+
.eq('user_id', user.id)
23+
.eq('is_default', true)
24+
.single()
2325

24-
if (teamError || !userTeam?.teams) {
25-
console.error('Team lookup failed for user:', user.id, teamError)
26-
// Return default free tier limits instead of error
27-
return NextResponse.json({
28-
subscription: {
29-
id: 'default',
30-
name: 'Personal',
31-
tier: 'free',
32-
subscription_status: 'active',
33-
cancel_at_period_end: false
34-
},
35-
usage_limits: [
36-
{
37-
usage_type: 'api_calls',
38-
limit_value: 100,
39-
current_usage: 0,
40-
period_start: new Date().toISOString(),
41-
period_end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
42-
}
43-
]
44-
})
45-
}
26+
const customerId = (teamData?.teams as any)?.stripe_customer_id
4627

47-
const team = userTeam.teams as any
48-
49-
const [subscription, usageLimits] = await Promise.all([
50-
getTeamSubscription(team.id),
51-
getTeamUsageLimits(team.id)
52-
])
28+
if (!customerId) {
29+
return NextResponse.json({ usage: [] })
30+
}
5331

54-
return NextResponse.json({
55-
subscription,
56-
usage_limits: usageLimits
32+
try {
33+
const subscriptions = await stripe.subscriptions.list({
34+
customer: customerId,
35+
status: 'active',
36+
expand: ['data.items'],
5737
})
38+
39+
if (!subscriptions.data.length) {
40+
return NextResponse.json({ usage: [] })
41+
}
42+
43+
const subscription = subscriptions.data[0]
44+
const usage = subscription.items.data.map((item) => ({
45+
id: item.id,
46+
quantity: item.quantity,
47+
price: {
48+
id: (item.price as any).id,
49+
unit_amount: (item.price as any).unit_amount,
50+
currency: (item.price as any).currency,
51+
product: (item.price as any).product,
52+
},
53+
}))
54+
55+
return NextResponse.json({ usage })
5856
} catch (error) {
59-
console.error('Usage API error:', error)
60-
return NextResponse.json(
61-
{ error: 'Failed to fetch usage data' },
62-
{ status: 500 }
63-
)
57+
console.error('Error fetching Stripe subscription usage:', error)
58+
return NextResponse.json({ error: 'Failed to fetch subscription usage' }, { status: 500 })
6459
}
65-
}
60+
}

app/api/templates/route.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { TemplateId } from '@/lib/templates'
3+
import { readFileSync } from 'fs'
4+
import { join } from 'path'
5+
6+
export interface TemplateFile {
7+
name: string
8+
content: string
9+
}
10+
11+
function getTemplateFiles(templateId: TemplateId): TemplateFile[] {
12+
const templateFiles: TemplateFile[] = []
13+
14+
try {
15+
const templateDir = join(process.cwd(), 'sandbox-templates', templateId)
16+
const fileMappings: Record<TemplateId, string[]> = {
17+
'code-interpreter-v1': ['script.py'],
18+
'nextjs-developer': ['_app.tsx', 'e2b.Dockerfile', 'e2b.toml', 'compile_page.sh'],
19+
'vue-developer': ['app.vue', 'e2b.Dockerfile', 'e2b.toml', 'nuxt.config.ts'],
20+
'streamlit-developer': ['app.py', 'e2b.Dockerfile', 'e2b.toml'],
21+
'gradio-developer': ['app.py', 'e2b.Dockerfile', 'e2b.toml']
22+
}
23+
24+
const files = fileMappings[templateId] || []
25+
26+
for (const file of files) {
27+
try {
28+
const filePath = join(templateDir, file)
29+
const content = readFileSync(filePath, 'utf-8')
30+
templateFiles.push({
31+
name: file,
32+
content
33+
})
34+
} catch (error) {
35+
console.warn(`Failed to read template file ${file}:`, error)
36+
}
37+
}
38+
} catch (error) {
39+
console.warn(`Failed to load template files for ${templateId}:`, error)
40+
}
41+
42+
return templateFiles
43+
}
44+
45+
export async function GET(req: NextRequest) {
46+
const { searchParams } = new URL(req.url)
47+
const templateId = searchParams.get('templateId') as TemplateId
48+
49+
if (!templateId) {
50+
return NextResponse.json({ error: 'templateId is required' }, { status: 400 })
51+
}
52+
53+
const templateFiles = getTemplateFiles(templateId)
54+
return NextResponse.json(templateFiles)
55+
}

app/api/webhooks/route.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { stripe } from '@/lib/stripe'
3+
import { handleSubscriptionEvent } from '@/lib/subscription'
4+
import Stripe from 'stripe'
5+
6+
export const dynamic = 'force-dynamic'
7+
8+
const relevantEvents = new Set([
9+
'checkout.session.completed',
10+
'customer.subscription.created',
11+
'customer.subscription.updated',
12+
'customer.subscription.deleted',
13+
'invoice.paid',
14+
'invoice.payment_succeeded',
15+
'invoice.payment_failed',
16+
])
17+
18+
export async function POST(request: NextRequest) {
19+
const body = await request.text()
20+
const sig = request.headers.get('stripe-signature')
21+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET
22+
23+
if (!sig || !webhookSecret) {
24+
console.error('Stripe signature or webhook secret is missing.')
25+
return NextResponse.json({ error: 'Webhook secret not configured' }, { status: 400 })
26+
}
27+
28+
let event: Stripe.Event
29+
30+
try {
31+
event = stripe!.webhooks.constructEvent(body, sig, webhookSecret)
32+
} catch (err: any) {
33+
console.error(`Webhook signature verification failed: ${err.message}`)
34+
return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 })
35+
}
36+
37+
if (relevantEvents.has(event.type)) {
38+
try {
39+
await handleSubscriptionEvent(event)
40+
} catch (error) {
41+
console.error('Error handling subscription event:', error)
42+
return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 })
43+
}
44+
}
45+
46+
return NextResponse.json({ received: true })
47+
}

flags.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,47 @@ export const subscriptionTier = flag<'free' | 'pro' | 'enterprise'>({
101101
identify,
102102
});
103103

104-
// Utility function for API routes - returns default values for now
104+
// Utility function for API routes - connects to GrowthBook feature flag service
105105
export async function getAllFeatureFlags() {
106-
// For now, return default values. In a real implementation, this would
107-
// connect to your feature flag service (GrowthBook, Edge Config, etc.)
108-
return {
109-
'workflow-builder-v2': false,
110-
'enhanced-code-editor': false,
111-
'premium-templates': false,
112-
'advanced-analytics': false,
113-
'beta-ai-models': false,
114-
'theme-customization': false,
115-
'subscription-tier': 'free' as const,
116-
};
106+
try {
107+
// TODO: Implement proper GrowthBook integration
108+
// For now, return default values as defined in flag declarations above
109+
// In production, this should connect to GrowthBook and return live flag values
110+
return {
111+
'workflow-builder-v2': false,
112+
'enhanced-code-editor': false,
113+
'premium-templates': false,
114+
'realtime-collaboration': false,
115+
'advanced-analytics': false,
116+
'beta-ai-models': false,
117+
'enterprise-features': false,
118+
'theme-customization': false,
119+
'deployment-integrations': false,
120+
'usage-limits-enforcement': true, // Always enforce limits by default
121+
'workflow-interface-style': 'list' as const,
122+
'subscription-tier': 'free' as const,
123+
'editor-theme-mode': 'basic' as const,
124+
};
125+
} catch (error) {
126+
console.error('Error fetching feature flags:', error);
127+
128+
// Return safe defaults if GrowthBook is unavailable
129+
return {
130+
'workflow-builder-v2': false,
131+
'enhanced-code-editor': false,
132+
'premium-templates': false,
133+
'realtime-collaboration': false,
134+
'advanced-analytics': false,
135+
'beta-ai-models': false,
136+
'enterprise-features': false,
137+
'theme-customization': false,
138+
'deployment-integrations': false,
139+
'usage-limits-enforcement': true, // Always enforce limits by default
140+
'workflow-interface-style': 'list' as const,
141+
'subscription-tier': 'free' as const,
142+
'editor-theme-mode': 'basic' as const,
143+
};
144+
}
117145
}
118146

119147
// Editor Theme Mode - Enhanced theme options

lib/analytics-service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { usePostHog } from 'posthog-js/react'
44
import { useAuth } from './auth'
5+
import { useUserTeam } from './user-team-provider'
56
import { FragmentSchema } from './schema'
67

78
// Analytics Event Types for Business Intelligence
@@ -407,7 +408,8 @@ export class AnalyticsService {
407408
// React Hook for Easy Usage
408409
export function useAnalytics() {
409410
const posthog = usePostHog()
410-
const { session, userTeam } = useAuth(() => {}, () => {})
411+
const { session } = useAuth(() => {}, () => {})
412+
const { userTeam } = useUserTeam()
411413
const analytics = AnalyticsService.getInstance()
412414

413415
// Initialize analytics service
@@ -429,4 +431,4 @@ export function useAnalytics() {
429431
}
430432

431433
// Import React for the useEffect hook
432-
import React from 'react'
434+
import React from 'react'

lib/auth-provider.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,10 @@
33
import { createContext, useContext, useState } from 'react'
44
import { useAuth } from './auth'
55
import { Session } from '@supabase/supabase-js'
6-
7-
type UserTeam = {
8-
email: string
9-
id: string
10-
name: string
11-
tier: string
12-
}
6+
import { UserTeamProvider } from './user-team-provider'
137

148
type AuthContextType = {
159
session: Session | null
16-
userTeam: UserTeam | undefined
1710
loading: boolean
1811
}
1912

@@ -22,11 +15,11 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined)
2215
export function AuthProvider({ children }: { children: React.ReactNode }) {
2316
const [authView, setAuthView] = useState<any>('sign_in')
2417
const [authDialog, setAuthDialog] = useState(false)
25-
const { session, userTeam, loading } = useAuth(setAuthDialog, setAuthView)
18+
const { session, loading } = useAuth(setAuthDialog, setAuthView)
2619

2720
return (
28-
<AuthContext.Provider value={{ session, userTeam, loading }}>
29-
{children}
21+
<AuthContext.Provider value={{ session, loading }}>
22+
<UserTeamProvider session={session}>{children}</UserTeamProvider>
3023
</AuthContext.Provider>
3124
)
3225
}

0 commit comments

Comments
 (0)