git clone < repository-ur l >
cd hackorda-mvp
# Database
DATABASE_URL = "postgresql://username:password@localhost:5432/hackorda_mvp"
# Clerk Authentication
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = "pk_test_..."
CLERK_SECRET_KEY = "sk_test_..."
NEXT_PUBLIC_CLERK_SIGN_IN_URL = "/sign-in"
NEXT_PUBLIC_CLERK_SIGN_UP_URL = "/sign-up"
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL = "/"
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL = "/"
# Generate migration files
npm run generate
# Run migrations
npm run migrate
# Seed database (optional)
npm run seed
npm run dev
# Visit http://localhost:3000
hackorda-mvp/
├── docs/ # Documentation files
├── public/ # Static assets
│ ├── blog/ # Markdown blog posts
│ └── images/ # Image assets
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── admin/ # Admin dashboard pages
│ │ ├── api/ # API routes
│ │ ├── auth/ # Authentication pages
│ │ ├── blog/ # Blog pages
│ │ ├── quiz/ # Quiz interface
│ │ └── globals.css # Global styles
│ ├── components/ # React components
│ │ ├── ui/ # Shadcn/ui components
│ │ ├── admin/ # Admin-specific components
│ │ ├── quiz/ # Quiz components
│ │ └── layout/ # Layout components
│ ├── db/ # Database configuration
│ │ ├── migrations/ # Drizzle migrations
│ │ ├── schema.ts # Database schema
│ │ └── seed.ts # Database seeding
│ ├── hooks/ # Custom React hooks
│ │ ├── admin/ # Admin hooks
│ │ ├── quiz/ # Quiz hooks
│ │ └── user/ # User hooks
│ ├── lib/ # Utility libraries
│ │ ├── auth/ # Authentication utilities
│ │ ├── blog/ # Blog utilities
│ │ └── utils.ts # General utilities
│ ├── types/ # TypeScript definitions
│ └── middleware.ts # Next.js middleware
├── tailwind.config.ts # TailwindCSS configuration
├── drizzle.config.ts # Drizzle ORM configuration
└── package.json # Dependencies and scripts
# Start development with fresh build
npm run dev
# Run linting
npm run lint
# Build for production
npm run build
# Start production server
npm run start
# Generate new migration
npm run generate
# Apply migrations
npm run migrate
# Reset and seed database (development only)
npm run seed
# Seed only users
npm run seedusers
{
"recommendations" : [
"bradlc.vscode-tailwindcss" ,
"ms-vscode.vscode-typescript-next" ,
"esbenp.prettier-vscode" ,
"ms-vscode.vscode-eslint" ,
"drizzle-team.drizzle-vscode"
]
}
{
"editor.formatOnSave" : true ,
"editor.defaultFormatter" : "esbenp.prettier-vscode" ,
"typescript.preferences.importModuleSpecifier" : "relative" ,
"tailwindCSS.experimental.classRegex" : [
"cn \\ (([^)]*) \\ )"
]
}
# 1. Modify src/db/schema.ts
# 2. Generate migration
npm run generate
# 3. Review generated migration in src/db/migrations/
# 4. Apply migration
npm run migrate
# 5. Update TypeScript types (auto-generated)
// Creating new table
export const newTable = pgTable ( "new_table" , {
id: serial ( "id" ). primaryKey (),
name: varchar ( "name" , { length: 255 }). notNull (),
createdAt: timestamp ( "created_at" ). defaultNow (). notNull ()
});
// Adding relationship
export const relatedTable = pgTable ( "related_table" , {
id: serial ( "id" ). primaryKey (),
newTableId: integer ( "new_table_id" ). references (() => newTable.id)
});
// src/components/example/ExampleComponent.tsx
"use client" ; // If client component needed
import { useState } from "react" ;
import { Button } from "@/components/ui/button" ;
interface ExampleComponentProps {
title : string ;
onAction ?: () => void ;
}
export function ExampleComponent ({ title , onAction } : ExampleComponentProps ) {
const [ isLoading , setIsLoading ] = useState ( false );
const handleClick = () => {
setIsLoading ( true );
onAction ?.();
setIsLoading ( false );
};
return (
< div className = "p-4" >
< h2 className = "text-xl font-bold" > {title} </ h2 >
< Button onClick = {handleClick} disabled = {isLoading} >
{ isLoading ? "Loading..." : "Action" }
</ Button >
</ div >
);
}
// src/hooks/example/useExample.ts
"use client" ;
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" ;
export function useExample () {
const queryClient = useQueryClient ();
const { data , isLoading , error } = useQuery ({
queryKey: [ 'example' ],
queryFn : async () => {
const response = await fetch ( '/api/example' );
if ( ! response.ok) throw new Error ( 'Failed to fetch' );
return response. json ();
},
staleTime: 5 * 60 * 1000 , // 5 minutes
gcTime: 10 * 60 * 1000 , // 10 minutes
});
const mutation = useMutation ({
mutationFn : async ( data : ExampleData ) => {
const response = await fetch ( '/api/example' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify (data)
});
if ( ! response.ok) throw new Error ( 'Failed to create' );
return response. json ();
},
onSuccess : () => {
queryClient. invalidateQueries ({ queryKey: [ 'example' ] });
}
});
return {
data,
isLoading,
error,
create: mutation.mutateAsync,
isCreating: mutation.isPending
};
}
// src/app/api/example/route.ts
import { NextRequest, NextResponse } from 'next/server' ;
import { db } from '@/db' ;
import { withAuth } from '@/lib/auth/api-middleware' ;
export async function GET ( request : NextRequest ) {
return withAuth (request, async ( req , userId ) => {
try {
// API logic here
const data = await db. select (). from (table);
return NextResponse. json (data);
} catch (error) {
console. error ( 'API Error:' , error);
return NextResponse. json (
{ error: 'Internal server error' },
{ status: 500 }
);
}
});
}
export async function POST ( request : NextRequest ) {
return withAuth (request, async ( req , userId ) => {
const body = await request. json ();
// Validate input
if ( ! body.requiredField) {
return NextResponse. json (
{ error: 'Required field missing' },
{ status: 400 }
);
}
// Process request
const result = await db. insert (table). values (body). returning ();
return NextResponse. json (result[ 0 ]);
});
}
// Use cn() utility for conditional classes
import { cn } from "@/lib/utils" ;
export function Component ({ variant , className } : Props ) {
return (
< div className = { cn (
"base classes here" ,
variant === "primary" && "primary variant classes" ,
variant === "secondary" && "secondary variant classes" ,
className
)} >
Content
</ div >
);
}
// Consistent spacing and sizing
< div className = "space-y-6 p-6" >
< div className = "flex items-center justify-between" >
< h1 className = "text-3xl font-bold tracking-tight" > Title </ h1 >
< Button >Action </ Button >
</ div >
< div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-3" >
{ /* Grid items */ }
</ div >
</ div >
npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom
// __tests__/components/ExampleComponent.test.tsx
import { render, screen } from '@testing-library/react' ;
import { ExampleComponent } from '@/components/ExampleComponent' ;
describe ( 'ExampleComponent' , () => {
it ( 'renders correctly' , () => {
render (< ExampleComponent title = "Test Title" />);
expect (screen. getByText ( 'Test Title' )). toBeInTheDocument ();
});
});
# Ensure clean build
npm run lint
npm run build
# Test production build locally
npm run start
# Database
DATABASE_URL = "postgresql://prod-db-url"
# Clerk (Production keys)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = "pk_live_..."
CLERK_SECRET_KEY = "sk_live_..."
# Check PostgreSQL is running
pg_isready -h localhost -p 5432
# Test connection
psql $DATABASE_URL
# Regenerate types
npm run generate
# Check environment variables
echo $NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
echo $CLERK_SECRET_KEY
// Add debug logging
console. log ( 'Debug:' , { variable, data });
// Use Next.js debugging
export const dynamic = 'force-dynamic' ; // In page.tsx
# Create feature branch
git checkout -b feature/new-feature
# Make changes and commit
git add .
git commit -m "feat: add new feature description"
# Push and create PR
git push origin feature/new-feature