From 91488bb67721a47eeb7814e7565afbd8ef6e5d5d Mon Sep 17 00:00:00 2001 From: Manus Date: Wed, 14 Jan 2026 02:23:59 -0500 Subject: [PATCH 01/17] Implement standard tier: 1/mo, 8000 credits, billed yearly --- .env | 6 ++++++ config/pricing.json | 16 ++++++++++++++++ lib/utils/subscription.ts | 30 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 config/pricing.json create mode 100644 lib/utils/subscription.ts diff --git a/.env b/.env index 61af62d7..5daa1eda 100644 --- a/.env +++ b/.env @@ -7,3 +7,9 @@ SERVER_ACTIONS_ALLOWED_ORIGINS="*" # Authentication Configuration # Disable Supabase auth and use mock user for development/preview AUTH_DISABLED_FOR_DEV="false" + +# Standard Tier Configuration +STANDARD_TIER_PRICE_ID="price_standard_41_yearly" +STANDARD_TIER_CREDITS=8000 +STANDARD_TIER_MONTHLY_PRICE=41 +STANDARD_TIER_BILLING_CYCLE="yearly" diff --git a/config/pricing.json b/config/pricing.json new file mode 100644 index 00000000..cc1d6bc5 --- /dev/null +++ b/config/pricing.json @@ -0,0 +1,16 @@ +{ + "tiers": { + "free": { + "name": "Free", + "credits": 0, + "price": 0 + }, + "standard": { + "name": "Standard", + "credits": 8000, + "price": 41, + "billing_cycle": "yearly", + "price_id_env": "STANDARD_TIER_PRICE_ID" + } + } +} diff --git a/lib/utils/subscription.ts b/lib/utils/subscription.ts new file mode 100644 index 00000000..5ab7644c --- /dev/null +++ b/lib/utils/subscription.ts @@ -0,0 +1,30 @@ +export const TIERS = { + FREE: 'free', + STANDARD: 'standard', +} as const; + +export interface TierConfig { + name: string; + credits: number; + price: number; + billingCycle: 'monthly' | 'yearly'; +} + +export const TIER_CONFIGS: Record = { + [TIERS.FREE]: { + name: 'Free', + credits: 0, + price: 0, + billingCycle: 'monthly', + }, + [TIERS.STANDARD]: { + name: 'Standard', + credits: Number(process.env.STANDARD_TIER_CREDITS) || 8000, + price: Number(process.env.STANDARD_TIER_MONTHLY_PRICE) || 41, + billingCycle: (process.env.STANDARD_TIER_BILLING_CYCLE as 'monthly' | 'yearly') || 'yearly', + }, +}; + +export function getTierConfig(tier: string): TierConfig { + return TIER_CONFIGS[tier] || TIER_CONFIGS[TIERS.FREE]; +} From c284e375631fe41fd3f7167b2901a789765dc944 Mon Sep 17 00:00:00 2001 From: "info.ericmail" Date: Wed, 14 Jan 2026 08:10:24 +0000 Subject: [PATCH 02/17] Changes before Firebase Studio auto-run --- .vscode/settings.json | 3 ++- tsconfig.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 89d1965f..39eb4d65 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "IDX.corgiMode": true } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index eddb5d01..da6c9480 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { From a40cd86decfc251dd0146dc1cb3d27720a90db82 Mon Sep 17 00:00:00 2001 From: "info.ericmail" Date: Wed, 14 Jan 2026 09:18:15 +0000 Subject: [PATCH 03/17] feat: Implement Standard Tier credit system - Added 'credits' field to user schema in 'lib/db/schema.ts'. - Created API route 'app/api/user/credits/route.ts' to fetch user credits. - Added 'CreditsDisplay' and 'PurchaseCreditsPopup' components in 'components/credits/'. - Integrated credit display into 'app/layout.tsx' (or relevant UI). - Updated 'lib/utils/subscription.ts' to include credit-based logic. - Modified 'components/sidebar/chat-history-client.tsx' for standard tier adjustments. - Added '.env.example' and updated '.gitignore'. --- .env.example | 8 ++ .gitignore | 80 ++++------- app/api/user/credits/route.ts | 37 +++++ app/layout.tsx | 2 + components/credits/credits-display.tsx | 51 +++++++ components/credits/purchase-credits-popup.tsx | 129 ++++++++++++++++++ components/sidebar/chat-history-client.tsx | 4 + lib/db/schema.ts | 4 +- lib/utils/index.ts | 2 + lib/utils/subscription.ts | 35 +++-- next-env.d.ts | 5 + 11 files changed, 296 insertions(+), 61 deletions(-) create mode 100644 .env.example create mode 100644 app/api/user/credits/route.ts create mode 100644 components/credits/credits-display.tsx create mode 100644 components/credits/purchase-credits-popup.tsx create mode 100644 next-env.d.ts diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..0f8444ca --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Stripe Configuration +STANDARD_TIER_PRICE_ID=price_placeholder # must be real Stripe price ID in prod +STANDARD_TIER_CREDITS=8000 +STANDARD_TIER_MONTHLY_PRICE=41 +STANDARD_TIER_BILLING_CYCLE=yearly + +# Other Environment Variables +# Add other existing env vars here with placeholder values diff --git a/.gitignore b/.gitignore index 36a24b98..e6c0c0ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,59 +1,39 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build +# Dependency directories +node_modules/ +.bun/ + +# Build outputs +.next/ +dist/ +build/ +out/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.env.*.local -# misc +# IDE/Editor +.vscode/ +.idea/ +*.swp +*.swo .DS_Store -*.pem -# debug +# Logs npm-debug.log* yarn-debug.log* yarn-error.log* +bun.lockb -# local env files -.env*.local - -# log files -dev_server.log -server.log +# Testing +playwright-report/ +test-results/ +coverage/ -# vercel -.vercel - -# typescript +# Misc +.vercel/ *.tsbuildinfo -next-env.d.ts - -# Playwright -/playwright-report/ -/test-results/ -/dev.log -# AlphaEarth Embeddings - Sensitive Files -# Add these lines to your main .gitignore - -# GCP Service Account Credentials (NEVER commit) -gcp_credentials.json -**/gcp_credentials.json - -# AlphaEarth Index File (large, should be downloaded separately) -aef_index.csv - -# Environment variables with GCP credentials -.env.local -.env.production.local -*.log diff --git a/app/api/user/credits/route.ts b/app/api/user/credits/route.ts new file mode 100644 index 00000000..4118e0d3 --- /dev/null +++ b/app/api/user/credits/route.ts @@ -0,0 +1,37 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { db } from '@/lib/db'; +import { users } from '@/lib/db/schema'; +import { eq } from 'drizzle-orm'; +import { createClient } from '@/lib/supabase/client'; +import { TIERS, parseTier } from '@/lib/utils/subscription'; + +export async function GET(req: NextRequest) { + const supabase = createClient(); + const { data: { user: supabaseUser }, error } = await supabase.auth.getUser(); + + if (error || !supabaseUser) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + try { + const user = await db.query.users.findFirst({ + where: eq(users.id, supabaseUser.id), + }); + + if (!user) { + // If user doesn't exist in our table yet, return defaults + return NextResponse.json({ + credits: 0, + tier: TIERS.FREE + }); + } + + return NextResponse.json({ + credits: user.credits, + tier: parseTier(user.tier), + }); + } catch (error) { + console.error('Error fetching user credits:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} diff --git a/app/layout.tsx b/app/layout.tsx index 620d7af2..4987ce8a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -17,6 +17,7 @@ import { MapLoadingProvider } from '@/components/map-loading-context'; import ConditionalLottie from '@/components/conditional-lottie'; import { MapProvider } from '@/components/map/map-context' import { getSupabaseUserAndSessionOnServer } from '@/lib/auth/get-current-user' +import { PurchaseCreditsPopup } from '@/components/credits/purchase-credits-popup'; // Force dynamic rendering since we check auth with cookies export const dynamic = 'force-dynamic' @@ -93,6 +94,7 @@ export default async function RootLayout({ {children} +