# nextjs-dashboard
> This skill provides guidance for building the VibeCheck user dashboard with Next.js.
- Author: fratellinho
- Repository: YvesMatteo/VibeCodeDetector
- Version: 20260208211634
- Stars: 0
- Forks: 0
- Last Updated: 2026-02-08
- Source: https://github.com/YvesMatteo/VibeCodeDetector
- Web: https://mule.run/skillshub/@@YvesMatteo/VibeCodeDetector~nextjs-dashboard:20260208211634
---
---
<<<<<<< Updated upstream
name: Next.js Dashboard Development
description: Build and maintain the VibeCheck dashboard UI with Next.js, React, and Tailwind CSS
---
# Next.js Dashboard Development
This skill provides guidance for building the VibeCheck user dashboard with Next.js.
## Tech Stack
| Component | Technology |
|-----------|------------|
| Framework | Next.js 14+ (App Router) |
| Styling | Tailwind CSS |
| UI Components | shadcn/ui |
| State | React hooks + Supabase realtime |
| Auth | Supabase Auth |
| Forms | React Hook Form + Zod |
=======
name: Next.js Dashboard
description: Patterns for building VibeCheck's dashboard UI with Next.js 14+, React, and Tailwind CSS
---
# Next.js Dashboard Development for VibeCheck
This skill covers UI patterns for building VibeCheck's user dashboard using Next.js 14+ App Router, React, TypeScript, and Tailwind CSS.
>>>>>>> Stashed changes
## Project Structure
```
src/
├── app/
│ ├── (auth)/
│ │ ├── login/page.tsx
<<<<<<< Updated upstream
│ │ ├── signup/page.tsx
│ │ └── reset-password/page.tsx
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ ├── page.tsx # Dashboard home
│ │ ├── scans/
│ │ │ ├── page.tsx # Scan history
│ │ │ ├── new/page.tsx # New scan
│ │ │ └── [id]/page.tsx # Scan details
│ │ ├── competitors/
│ │ │ └── page.tsx
│ │ ├── reports/
│ │ │ └── page.tsx
│ │ └── settings/
│ │ └── page.tsx
│ ├── api/
│ │ ├── scans/route.ts
│ │ └── webhooks/
│ │ └── stripe/route.ts
│ ├── layout.tsx
│ └── page.tsx # Landing page
├── components/
│ ├── ui/ # shadcn components
│ ├── dashboard/
│ │ ├── sidebar.tsx
│ │ ├── header.tsx
│ │ └── scan-card.tsx
│ ├── scans/
│ │ ├── scan-form.tsx
│ │ ├── scan-results.tsx
│ │ └── score-gauge.tsx
│ └── charts/
│ └── findings-chart.tsx
├── lib/
│ ├── supabase/
│ │ ├── client.ts
│ │ ├── server.ts
│ │ └── middleware.ts
│ ├── stripe.ts
│ └── utils.ts
├── hooks/
│ ├── use-user.ts
│ ├── use-scans.ts
│ └── use-subscription.ts
└── types/
└── database.ts # Generated Supabase types
```
## Project Initialization
```bash
# Create Next.js app
npx -y create-next-app@latest ./ --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
# Install dependencies
npm install @supabase/supabase-js @supabase/ssr stripe @stripe/stripe-js
npm install react-hook-form @hookform/resolvers zod
npm install recharts lucide-react
# Initialize shadcn/ui
npx -y shadcn@latest init
npx -y shadcn@latest add button card input form dialog sheet
```
## Supabase Client Setup
### Browser Client (`lib/supabase/client.ts`)
```typescript
import { createBrowserClient } from '@supabase/ssr';
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
=======
│ │ └── signup/page.tsx
│ ├── (dashboard)/
│ │ ├── layout.tsx # Dashboard layout with sidebar
│ │ ├── page.tsx # Dashboard home
│ │ ├── scans/
│ │ │ ├── page.tsx # Scan history
│ │ │ ├── new/page.tsx # New scan form
│ │ │ └── [id]/page.tsx # Scan results
│ │ ├── competitors/page.tsx
│ │ └── settings/page.tsx
│ ├── api/
│ │ └── scans/route.ts
│ ├── layout.tsx
│ └── page.tsx # Landing page
├── components/
│ ├── ui/ # Reusable UI components
│ ├── dashboard/ # Dashboard-specific components
│ └── scanners/ # Scanner result components
├── lib/
│ ├── supabase/
│ └── utils.ts
└── types/
```
## Dashboard Layout
```tsx
// app/(dashboard)/layout.tsx
import { Sidebar } from '@/components/dashboard/sidebar';
import { Header } from '@/components/dashboard/header';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
{score}
{label}
=======
## Key Dashboard Components
### Score Display Component
```tsx
// components/dashboard/score-display.tsx
interface ScoreDisplayProps {
score: number;
label: string;
size?: 'sm' | 'md' | 'lg';
}
export function ScoreDisplay({ score, label, size = 'md' }: ScoreDisplayProps) {
const getColor = (score: number) => {
if (score >= 80) return 'text-green-500';
if (score >= 60) return 'text-yellow-500';
return 'text-red-500';
};
const sizes = {
sm: 'text-2xl',
md: 'text-4xl',
lg: 'text-6xl',
};
return (
{score}
{label}
>>>>>>> Stashed changes
);
}
```
<<<<<<< Updated upstream
### Findings Summary Card
```typescript
interface FindingsSummaryProps {
findings: {
critical: number;
high: number;
medium: number;
low: number;
};
}
export function FindingsSummary({ findings }: FindingsSummaryProps) {
return (
🔴 {findings.critical} Critical
🟠 {findings.high} High
🟡 {findings.medium} Medium
🟢 {findings.low} Low
=======
### Findings List Component
```tsx
// components/scanners/findings-list.tsx
import { Finding } from '@/types/scanner';
const severityColors = {
critical: 'bg-red-100 text-red-800 border-red-200',
high: 'bg-orange-100 text-orange-800 border-orange-200',
medium: 'bg-yellow-100 text-yellow-800 border-yellow-200',
low: 'bg-blue-100 text-blue-800 border-blue-200',
info: 'bg-gray-100 text-gray-800 border-gray-200',
};
export function FindingsList({ findings }: { findings: Finding[] }) {
return (
{findings.map((finding) => (
{finding.title}
{finding.severity}
{finding.description}
{finding.location && (
{finding.location}
)}
))}
>>>>>>> Stashed changes
);
}
```
<<<<<<< Updated upstream
## API Routes
### Create Scan
```typescript
// app/api/scans/route.ts
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { targetUrl, scanTypes } = await request.json();
// Check scan limits
const { data: profile } = await supabase
.from('user_profiles')
.select('scans_used, scans_limit')
.eq('id', user.id)
.single();
if (profile && profile.scans_used >= profile.scans_limit) {
return NextResponse.json({ error: 'Scan limit reached' }, { status: 403 });
}
// Create scan
const { data: scan, error } = await supabase
.from('scans')
.insert({
user_id: user.id,
target_url: targetUrl,
scan_types: scanTypes,
status: 'pending',
})
.select()
.single();
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
// Increment scans_used
await supabase
.from('user_profiles')
.update({ scans_used: profile.scans_used + 1 })
.eq('id', user.id);
// TODO: Queue scan job
return NextResponse.json(scan);
}
```
## Real-time Scan Updates
```typescript
=======
### Scan Progress Component
```tsx
// components/scanners/scan-progress.tsx
>>>>>>> Stashed changes
'use client';
import { useEffect, useState } from 'react';
import { createClient } from '@/lib/supabase/client';
<<<<<<< Updated upstream
export function useScanStatus(scanId: string) {
const [status, setStatus] = useState
('pending');
const supabase = createClient();
useEffect(() => {
const channel = supabase
.channel(`scan:${scanId}`)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'scans',
filter: `id=eq.${scanId}`,
},
(payload) => {
setStatus(payload.new.status);
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [scanId]);
return status;
}
```
## Environment Variables
```env
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
```
## Styling Guidelines
1. **Dark Mode First** - Design for dark mode, add light mode support
2. **Use CSS Variables** - Leverage Tailwind's CSS variable system
3. **Consistent Spacing** - Use Tailwind's spacing scale (p-4, gap-6, etc.)
4. **Responsive** - Mobile-first, use sm/md/lg breakpoints
5. **Animations** - Subtle transitions (150-300ms) for interactions
## Performance Tips
1. Use React Server Components where possible
2. Implement loading.tsx for streaming
3. Use Image component for optimized images
4. Lazy load heavy components (charts, editors)
5. Cache API responses with React Query or SWR
=======
export function ScanProgress({ scanId }: { scanId: string }) {
const [status, setStatus] = useState('pending');
const [progress, setProgress] = useState(0);
useEffect(() => {
const supabase = createClient();
const channel = supabase
.channel(`scan-${scanId}`)
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'scans',
filter: `id=eq.${scanId}`,
}, (payload) => {
setStatus(payload.new.status);
// Calculate progress based on completed scanner types
})
.subscribe();
return () => { supabase.removeChannel(channel); };
}, [scanId]);
return (
);
}
```
## New Scan Form
```tsx
// app/(dashboard)/scans/new/page.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
const SCAN_TYPES = [
{ id: 'security', label: 'Security', icon: '🔒' },
{ id: 'api-keys', label: 'API Key Leaks', icon: '🔑' },
{ id: 'seo', label: 'SEO', icon: '📈' },
{ id: 'legal', label: 'Legal Compliance', icon: '⚖️' },
{ id: 'ai-detection', label: 'AI Detection', icon: '🤖' },
];
export default function NewScanPage() {
const router = useRouter();
const [url, setUrl] = useState('');
const [selectedTypes, setSelectedTypes] = useState(['security']);
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
const res = await fetch('/api/scans', {
method: 'POST',
body: JSON.stringify({ url, scanTypes: selectedTypes }),
});
const { scanId } = await res.json();
router.push(`/scans/${scanId}`);
};
return (
);
}
```
## Design System Colors
Use consistent colors across the dashboard:
```css
/* globals.css */
:root {
--color-critical: 239 68 68; /* red-500 */
--color-high: 249 115 22; /* orange-500 */
--color-medium: 234 179 8; /* yellow-500 */
--color-low: 59 130 246; /* blue-500 */
--color-success: 34 197 94; /* green-500 */
}
```
## Best Practices
1. **Use Server Components** by default, add 'use client' only when needed
2. **Implement loading states** using `loading.tsx` files
3. **Handle errors** with `error.tsx` boundaries
4. **Optimize images** with `next/image`
5. **Use Suspense** for streaming and progressive loading
6. **Implement proper SEO** with metadata API
>>>>>>> Stashed changes