-
-
Notifications
You must be signed in to change notification settings - Fork 7
Implement Standard Tier: 1/mo, 8000 credits, billed yearly #428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ngoiyaeric
wants to merge
18
commits into
refactor/collaboration-ui-integration
Choose a base branch
from
feature/standard-tier-implementation
base: refactor/collaboration-ui-integration
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
91488bb
Implement standard tier: 1/mo, 8000 credits, billed yearly
CJWTRUST c284e37
Changes before Firebase Studio auto-run
ngoiyaeric a40cd86
feat: Implement Standard Tier credit system
ngoiyaeric 1f9de85
fix: update import in app/api/user/credits/route.ts
ngoiyaeric 696c177
fix: add schema to drizzle instance in lib/db/index.ts
ngoiyaeric f30205b
fix: use getTierConfig in app/api/user/credits/route.ts
ngoiyaeric 584015e
fix(auth,credits): enhance oauth error logging and integrate credits …
google-labs-jules[bot] d6165e8
Merge pull request #437 from QueueLab/fix-oauth-and-credits-integrati…
ngoiyaeric 8f2fa4e
Fix: Stripe popup not showing by using useCurrentUser hook
ngoiyaeric 2a39100
Fix: Removed 7-day cooldown and used sessionStorage for easier testin…
ngoiyaeric 7e9679a
Refactor: Connect credit popup to header icon and add basic test file
ngoiyaeric f38c5a2
Fix: Make history icon visible on desktop and use Sprout icon
ngoiyaeric 9174d24
Fix: Add Stripe checkout, webhook, user init, and credit consumption …
ngoiyaeric b1d2875
Fix: Update Stripe API version to match SDK
ngoiyaeric 08a6c9b
Fix: Update Stripe API version to '2025-12-15.clover' with type asser…
ngoiyaeric 02b1f99
Fix: Await headers() in Stripe webhook for Next.js 16 compatibility
ngoiyaeric 008989c
fix: add fallback stripe keys for build
ngoiyaeric f815d08
fix: dependency resolution and build issues
ngoiyaeric File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Supabase Configuration | ||
| NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co | ||
| NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here | ||
|
|
||
| # 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| { | ||
| "editor.formatOnSave": true, | ||
| "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| "editor.defaultFormatter": "esbenp.prettier-vscode", | ||
| "IDX.corgiMode": true | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
| import Stripe from 'stripe'; | ||
| import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'; | ||
|
|
||
| // Ensure Stripe is initialized with the secret key | ||
| const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || 'sk_test_dummy', { | ||
| apiVersion: '2025-12-15.clover' as any, // Use the specific version expected by the SDK | ||
| }); | ||
|
|
||
| export async function POST(req: NextRequest) { | ||
| try { | ||
| const userId = await getCurrentUserIdOnServer(); | ||
| if (!userId) { | ||
| return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); | ||
| } | ||
|
|
||
| const body = await req.json(); | ||
| const { priceId, returnUrl } = body; | ||
|
|
||
| if (!priceId) { | ||
| return NextResponse.json({ error: 'Price ID is required' }, { status: 400 }); | ||
| } | ||
|
|
||
| const session = await stripe.checkout.sessions.create({ | ||
| mode: 'subscription', // or 'payment' for one-time | ||
| payment_method_types: ['card'], | ||
| line_items: [ | ||
| { | ||
| price: priceId, | ||
| quantity: 1, | ||
| }, | ||
| ], | ||
| metadata: { | ||
| userId: userId, // Pass userId to metadata for webhook | ||
| }, | ||
| success_url: `${returnUrl || req.headers.get('origin')}/?success=true`, | ||
| cancel_url: `${returnUrl || req.headers.get('origin')}/?canceled=true`, | ||
| }); | ||
|
|
||
| return NextResponse.json({ url: session.url }); | ||
| } catch (error: any) { | ||
| console.error('Error creating checkout session:', error); | ||
| return NextResponse.json({ error: error.message }, { status: 500 }); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
| import { db } from '@/lib/db'; | ||
| import { users } from '@/lib/db/schema'; | ||
| import { eq } from 'drizzle-orm'; | ||
| import { getSupabaseServerClient } from '@/lib/supabase/client'; | ||
| import { TIERS, parseTier, getTierConfig } from '@/lib/utils/subscription'; | ||
|
|
||
| export async function GET(req: NextRequest) { | ||
| try { | ||
| const supabase = getSupabaseServerClient(); | ||
| const { | ||
| data: { user }, | ||
| error: userError | ||
| } = await supabase.auth.getUser(); | ||
|
|
||
| if (userError || !user) { | ||
| return NextResponse.json( | ||
| { error: 'Unauthorized' }, | ||
| { status: 401 } | ||
| ); | ||
| } | ||
|
|
||
| // Get user from database | ||
| const dbUser = await db.query.users.findFirst({ | ||
| where: eq(users.id, user.id) | ||
| }); | ||
|
|
||
| if (!dbUser) { | ||
| return NextResponse.json( | ||
| { error: 'User not found' }, | ||
| { status: 404 } | ||
| ); | ||
| } | ||
|
|
||
| const tier = parseTier(dbUser.tier); | ||
| // If user is not on Standard tier, they might not need credits logic, | ||
| // but for now we return the credits regardless. | ||
| // If the tier doesn't support credits (e.g. Free or Pro), the UI can handle it. | ||
|
|
||
| return NextResponse.json({ | ||
| credits: dbUser.credits, | ||
| tier: tier, | ||
| features: getTierConfig(tier) | ||
| }); | ||
|
|
||
| } catch (error) { | ||
| console.error('Error fetching user credits:', error); | ||
| return NextResponse.json( | ||
| { error: 'Internal Server Error' }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import { headers } from 'next/headers'; | ||
| import { NextResponse } from 'next/server'; | ||
| import Stripe from 'stripe'; | ||
| import { getSupabaseServiceClient } from '@/lib/supabase/client'; | ||
| import { TIER_CONFIGS, TIERS } from '@/lib/utils/subscription'; | ||
|
|
||
| const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || 'sk_test_dummy', { | ||
| apiVersion: '2025-12-15.clover' as any, | ||
| }); | ||
|
|
||
| const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET || 'whsec_dummy'; | ||
|
|
||
| export async function POST(req: Request) { | ||
| const body = await req.text(); | ||
| const headersList = await headers(); // Await headers() as per Next.js 15+ / 16 | ||
| const signature = headersList.get('stripe-signature') as string; | ||
|
|
||
| let event: Stripe.Event; | ||
|
|
||
| try { | ||
| event = stripe.webhooks.constructEvent(body, signature, webhookSecret); | ||
| } catch (err: any) { | ||
| console.error(`Webhook signature verification failed: ${err.message}`); | ||
| return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 }); | ||
| } | ||
|
|
||
| const supabase = getSupabaseServiceClient(); | ||
|
|
||
| try { | ||
| switch (event.type) { | ||
| case 'checkout.session.completed': { | ||
| const session = event.data.object as Stripe.Checkout.Session; | ||
| const userId = session.metadata?.userId; | ||
|
|
||
| if (userId) { | ||
| console.log(`Processing checkout for user: ${userId}`); | ||
| // Update user credits and tier | ||
| // Assuming Standard Tier for this example, or derive from session | ||
| const standardCredits = TIER_CONFIGS[TIERS.STANDARD].credits; | ||
|
|
||
| const { error } = await supabase | ||
| .from('users') | ||
| .update({ | ||
| credits: standardCredits, | ||
| tier: 'standard', | ||
| // updated_at: new Date().toISOString() | ||
| }) | ||
| .eq('id', userId); | ||
|
|
||
| if (error) { | ||
| console.error('Error updating user credits in Supabase:', error); | ||
| throw error; | ||
| } | ||
| console.log(`Updated credits for user ${userId} to ${standardCredits}`); | ||
| } | ||
| break; | ||
| } | ||
| // Handle other event types | ||
| default: | ||
| console.log(`Unhandled event type ${event.type}`); | ||
| } | ||
| } catch (error) { | ||
| console.error('Error handling webhook event:', error); | ||
| return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 }); | ||
| } | ||
|
|
||
| return NextResponse.json({ received: true }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider renaming to
.env.exampleand documenting placeholders.Committing a
.envfile can accidentally leak secrets if real values are added later. The standard practice is to commit.env.examplewith placeholder values and add.envto.gitignore.Also,
price_standard_41_yearlyappears to be a placeholder Stripe price ID—consider adding a comment indicating it must be replaced with a real Stripe price ID in production.Suggested changes
📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 12-12: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 13-13: [UnorderedKey] The STANDARD_TIER_CREDITS key should go before the STANDARD_TIER_PRICE_ID key
(UnorderedKey)
[warning] 14-14: [UnorderedKey] The STANDARD_TIER_MONTHLY_PRICE key should go before the STANDARD_TIER_PRICE_ID key
(UnorderedKey)
[warning] 15-15: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 15-15: [UnorderedKey] The STANDARD_TIER_BILLING_CYCLE key should go before the STANDARD_TIER_CREDITS key
(UnorderedKey)
🤖 Prompt for AI Agents