Skip to content
Open
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
16 changes: 4 additions & 12 deletions .env
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
AUTH_DISABLED_FOR_DEV=false
DATABASE_URL="postgresql://user:password@host:port/db"

# Server Actions Configuration
# Allow Server Actions in remote dev environments
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"
SERVER_ACTIONS_ALLOWED_ORIGINS=*
STANDARD_TIER_BILLING_CYCLE="yearly"
STANDARD_TIER_CREDITS=8000
STANDARD_TIER_MONTHLY_PRICE=41
STANDARD_TIER_BILLING_CYCLE="yearly"
STANDARD_TIER_PRICE_ID="price_standard_41_yearly"
12 changes: 3 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
# 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
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
STANDARD_TIER_BILLING_CYCLE="yearly"
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
STANDARD_TIER_PRICE_ID="price_placeholder"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ out/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Lock files
bun.lockb

# Testing
playwright-report/
test-results/
coverage/

# Supabase local CLI state
supabase/.temp/

# Misc
.vercel/
*.tsbuildinfo
5 changes: 2 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"IDX.corgiMode": true
}
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
2 changes: 1 addition & 1 deletion FIXES_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ This commit addresses critical security vulnerabilities and auth backend schema
## Related Issues

Addresses CodeRabbit review comments:
- https://github.com/QueueLab/QCX/pull/327#issuecomment-3714336689
- [CodeRabbit Review Comment](https://github.com/QueueLab/QCX/pull/327#issuecomment-3714336689)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@



[**Pricing**](https://buy.stripe.com/14A3cv7K72TR3go14Nasg02)  |  [**Land**](https://wwww.queue.cx)  |  [**X**](https://x.com/tryqcx)
[**Pricing**]  |  [**Land**](https://wwww.queue.cx)  |  [**X**](https://x.com/tryqcx)

<a href="https://www.producthunt.com/products/qcx?embed=true&utm_source=badge-featured&utm_medium=badge&utm_source=badge-qcx" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1035588&theme=light&t=1762583679476" alt="QCX - Artificial&#0032;General&#0032;Intelligence&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
Expand Down
23 changes: 18 additions & 5 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { inquire, researcher, taskManager, querySuggestor, resolutionSearch } fr
// The geospatialTool (if used by agents like researcher) now manages its own MCP client.
import { writer } from '@/lib/agents/writer'
import { saveChat, getSystemPrompt } from '@/lib/actions/chat' // Added getSystemPrompt
import { getCurrentUserIdOnServer } from '@/lib/auth/get-current-user'
import { retrieveContext } from '@/lib/actions/rag'
import { Chat, AIMessage } from '@/lib/types'
import { UserMessage } from '@/components/user-message'
Expand Down Expand Up @@ -324,7 +325,10 @@ async function submit(formData?: FormData, skip?: boolean) {
} as CoreMessage)
}

const userId = 'anonymous'
const userId = await getCurrentUserIdOnServer()
if (!userId) {
throw new Error('Unauthorized')
}
const currentSystemPrompt = (await getSystemPrompt(userId)) || ''

const retrievedContext = userInput
Expand Down Expand Up @@ -544,7 +548,11 @@ export const AI = createAI<AIState, UIState>({

const { chatId, messages } = state

const userId = 'anonymous'
const userId = await getCurrentUserIdOnServer()

if (!userId) {
return
}

const lastMessage = messages[messages.length - 1]
if (lastMessage && lastMessage.role === 'assistant' && done) {
Expand Down Expand Up @@ -595,9 +603,14 @@ export const getUIStateFromAIState = (aiState: Chat) => {
let messageContent: string | any[]
try {
// For backward compatibility with old messages that stored a JSON string
const json = JSON.parse(content as string)
messageContent =
type === 'input' ? json.input : json.related_query
const parsed = JSON.parse(content as string)
if (Array.isArray(parsed)) {
messageContent = parsed
} else if (typeof parsed === 'object' && parsed !== null) {
messageContent = type === 'input' ? parsed.input : parsed.related_query
} else {
messageContent = parsed
}
Comment on lines 603 to +613

Choose a reason for hiding this comment

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

messageContent: string | any[] reintroduces any and undermines the benefit of the parsing normalization. Since the downstream component apparently supports string | unknown[] (or similar), keep it type-safe without any.

Suggestion

Avoid any here; use unknown[] (or a domain type for multimodal chunks).

let messageContent: string | unknown[]

If you know the shape, define a MultimodalPart union and use MultimodalPart[].

Reply with "@CharlieHelps yes please" if you want me to push a commit that replaces any[] with unknown[] (and optionally adds a small type alias).

} catch (e) {
// New messages will store the content array or string directly
messageContent = content
Expand Down
2 changes: 1 addition & 1 deletion app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export async function POST(request: NextRequest) {
userId: userId,
title: title || 'New Chat',
createdAt: new Date(),
path: `/chat/${chatId}`,
path: `/search/${chatId}`,
messages: [
{
id: uuidv4(),
Expand Down
83 changes: 83 additions & 0 deletions app/api/user/upgrade/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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 { TIER_CONFIGS, TIERS, parseTier } from '@/lib/utils/subscription';

export async function POST(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 }
);
}

const { tier } = await req.json();

// Validate tier
if (!tier || !Object.values(TIERS).includes(tier)) {
return NextResponse.json(
{ error: 'Invalid tier' },
{ status: 400 }
);
}

// Get tier config to determine credits to add
const tierConfig = TIER_CONFIGS[tier as keyof typeof TIER_CONFIGS];
if (!tierConfig) {
return NextResponse.json(
{ error: 'Tier not found' },
{ status: 400 }
);
}

// Get current user from database
const currentUser = await db.query.users.findFirst({
where: eq(users.id, user.id)
});

if (!currentUser) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}

// Calculate credits to add
const creditsToAdd = tierConfig.credits;
const newCreditsTotal = currentUser.credits + creditsToAdd;

// Update user in database with new tier and credits
const updatedUser = await db
.update(users)
.set({
tier: tier,
credits: newCreditsTotal
})
.where(eq(users.id, user.id))
.returning();

return NextResponse.json({
success: true,
tier: tier,
creditsAdded: creditsToAdd,
totalCredits: newCreditsTotal,
message: `Successfully upgraded to ${tier} tier with ${creditsToAdd} credits`
});

} catch (error) {
console.error('Error upgrading user:', error);
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
}
}
4 changes: 2 additions & 2 deletions app/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useAuth } from "@/lib/auth/v0"
function Logo() {
return (
<div className="flex items-center gap-2 text-xl font-semibold">
<Image src="/images/logo.png" alt="QCX Logo" width={32} height={32} />
<Image src="/images/logo-green.png" alt="QCX Logo" width={32} height={32} />
QCX
</div>
)
Expand All @@ -18,7 +18,7 @@ function Logo() {
function ArtPanel() {
return (
<div className="relative flex h-full w-full items-center justify-center overflow-hidden rounded-3xl">
<Image src="/images/satellite-collage.png" alt="Abstract art" fill className="object-cover" priority />
<Image src="/images/abstract-art.png" alt="Abstract art" fill className="object-cover" priority />
</div>
)
}
Expand Down
3 changes: 3 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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';
import { CreditsProvider } from '@/components/credits/credits-provider';

// Force dynamic rendering since we check auth with cookies
export const dynamic = 'force-dynamic'
Expand Down Expand Up @@ -90,11 +91,13 @@ export default async function RootLayout({
<ProfileToggleProvider>
<MapProvider>
<MapLoadingProvider>
<CreditsProvider>
<Header />
<ConditionalLottie />
{children}
<Sidebar />
<PurchaseCreditsPopup />
</CreditsProvider>
<Footer />
<Toaster />
</MapLoadingProvider>
Expand Down
Loading