Expert development assistant for Finance Scoop - a Next.js 16 social media monitoring tool. Enforces modern React patterns (React Query, Zustand, no useEffect), proper import ordering, and Next.js 15+ conventions.
Expert development assistant for Finance Scoop - an AI-powered Next.js 16 application that monitors social media (Reddit, Twitter) for finance discussions and generates smart reply drafts using xAI (Grok).
**Repository**: https://github.com/iamtxena/finance-scoop
**Tech Stack**: Next.js 16, React 19, TypeScript, TanStack React Query, Zustand, Clerk, Supabase, xAI (Grok)
**ALWAYS use TanStack React Query** for all data fetching operations. Never use useEffect for API calls.
```typescript
// ❌ NEVER DO THIS
useEffect(() => {
fetch('/api/alerts').then(res => setData(res));
}, []);
// ✅ ALWAYS DO THIS
const { data: alerts } = useQuery({
queryKey: ['alerts'],
queryFn: async () => {
const { data } = await axios.get('/api/alerts');
return data.alerts;
},
});
```
**CRITICAL**: All imports MUST be at the top of the file. Never create imports in the middle of code.
```typescript
// 1. External imports
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
// 2. Internal components
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
// 3. Hooks
import { useAlerts } from '@/hooks/use-alerts';
// 4. Lib/utils
import { createClient } from '@/lib/supabase/client';
// 5. Types
import type { Alert } from '@/hooks/use-alerts';
```
Always await params in route handlers:
```typescript
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params; // ✅ Must await
}
```
```
src/
├── app/ # Next.js App Router
│ ├── (auth)/ # Route groups (don't affect URL)
│ ├── (dashboard)/ # Protected routes
│ └── api/ # API route handlers
├── components/
│ ├── ui/ # shadcn/ui base components (don't edit)
│ └── features/ # Feature-specific components
├── hooks/ # React Query hooks (use-*.ts)
├── stores/ # Zustand stores (*-store.ts)
├── lib/
│ ├── supabase/ # Database clients
│ ├── reddit/ # Reddit API wrapper
│ ├── ai/ # AI agents and prompts
│ ├── redis/ # Cache helpers
│ └── notifications/ # Email/Slack
└── types/ # TypeScript type definitions
```
1. **Create directory structure**
```bash
mkdir -p src/app/api/new-feature
touch src/app/api/new-feature/route.ts
```
2. **Implement with this template**
```typescript
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@clerk/nextjs/server';
export async function GET(request: NextRequest) {
try {
// 1. Auth check
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 2. Business logic
// ...
return NextResponse.json({ data: result });
} catch (error) {
console.error('Error in GET /api/new-feature:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Internal server error' },
{ status: 500 }
);
}
}
```
1. **Create hook file**: `src/hooks/use-feature-name.ts`
2. **Implement query and mutation**
```typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
export function useFeatureName() {
return useQuery({
queryKey: ['feature-name'],
queryFn: async () => {
const { data } = await axios.get('/api/feature-name');
return data;
},
});
}
export function useCreateFeatureName() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: any) => {
const { data } = await axios.post('/api/feature-name', input);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['feature-name'] });
},
});
}
```
1. **Create page file**: `src/app/(dashboard)/page-name/page.tsx`
2. **Use this template**
```typescript
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useFeatureName } from '@/hooks/use-feature-name';
export default function PageName() {
const { data, isLoading, error } = useFeatureName();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data || data.length === 0) return <div>No data found</div>;
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold">Page Title</h1>
<Card>
<CardHeader>
<CardTitle>Data</CardTitle>
</CardHeader>
<CardContent>
{/* Render data */}
</CardContent>
</Card>
</div>
);
}
```
1. **Create store file**: `src/stores/feature-name-store.ts`
2. **Implement store**
```typescript
import { create } from 'zustand';
interface FeatureState {
items: string[];
addItem: (item: string) => void;
removeItem: (index: number) => void;
reset: () => void;
}
export const useFeatureStore = create<FeatureState>((set) => ({
items: [],
addItem: (item) =>
set((state) => ({ items: [...state.items, item] })),
removeItem: (index) =>
set((state) => ({ items: state.items.filter((_, i) => i !== index) })),
reset: () => set({ items: [] }),
}));
```
1. **Create migration**: `supabase/migrations/00X_table_name.sql`
2. **Define schema with RLS**
```sql
CREATE TABLE IF NOT EXISTS table_name (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id TEXT NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_table_name_user_id ON table_name(user_id);
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view their own rows"
ON table_name FOR SELECT
USING (auth.jwt() ->> 'sub' = user_id);
CREATE POLICY "Users can manage their own rows"
ON table_name FOR ALL
USING (auth.jwt() ->> 'sub' = user_id);
```
3. **Update types**: Add table definition to `src/lib/supabase/types.ts`
1. **Add agent to**: `src/lib/ai/agents.ts`
2. **Use traceable pattern**
```typescript
import { xai } from '@ai-sdk/xai';
import { traceable } from 'langsmith/traceable';
import { generateText } from './client';
export const newAgent = traceable(
async (input: string): Promise<string> => {
const { text } = await generateText({
model: xai('grok-4-fast-reasoning'),
prompt: `Your prompt here: ${input}`,
temperature: 0.3,
});
return text.trim();
},
{
name: 'new-agent-name',
run_type: 'llm',
metadata: {
model: 'grok-4-fast-reasoning',
task: 'description',
},
}
);
```
```typescript
// Server-side (API routes)
import { createClient } from '@/lib/supabase/server';
const supabase = await createClient();
// Client-side (React components)
import { createClient } from '@/lib/supabase/client';
const supabase = createClient();
```
```typescript
import { cacheGet, cacheSet } from '@/lib/redis/client';
export async function getUserPosts(username: string) {
const cacheKey = `reddit:user:${username}:posts`;
const cached = await cacheGet<RedditPost[]>(cacheKey);
if (cached) return cached;
const posts = await fetchFromReddit(username);
await cacheSet(cacheKey, posts, 300); // 5 minutes
return posts;
}
```
Before completing any task:
❌ **Never import in middle of file**
❌ **Never use useEffect for data fetching**
❌ **Never forget to await params in Next.js 15+ routes**
❌ **Never edit shadcn/ui components directly** (in `components/ui/`)
❌ **Never use `any` type** - use `unknown` or proper types
**Required**: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, XAI_API_KEY, REDDIT_APP_ID, REDDIT_APP_SECRET, REDDIT_USERNAME, REDDIT_PASSWORD, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
**Optional**: LANGCHAIN_API_KEY, RESEND_API_KEY, SLACK_WEBHOOK_URL, CRON_SECRET
```bash
pnpm dev # Start dev server
pnpm build # Build for production
pnpm lint # Run ESLint
pnpm add pkg # Add dependency
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/finance-scoop-development-guide/raw