What: React 18 frontend for CodePatchwork (visual code snippet manager)
Tech: TypeScript + Vite + TailwindCSS + Shadcn UI + TanStack Query + Wouter
# Install dependencies (from project root)
npm install
# Start dev server (runs both client and server)
npm run dev
# Client served by Vite dev server (usually http://localhost:5173)
# Build client only
npm run build
# Output: dist/public/
# Typecheck client + server
npm run checkclient/src/
├── components/ # UI components
│ ├── ui/ # Shadcn UI primitives (Button, Dialog, etc.)
│ ├── *Dialog.tsx # Modal/dialog components
│ ├── *Card.tsx # Card-based display components
│ └── *.tsx # Feature components
├── pages/ # Route pages (Home, Snippets, Collections, Settings)
├── hooks/ # Custom React hooks (useSnippets, useAuthenticatedFetch, use-toast)
├── contexts/ # React Context providers (AuthContext, ThemeContext, etc.)
├── lib/ # Utilities and configuration
│ ├── firebase.ts # Firebase client SDK
│ ├── queryClient.ts # TanStack Query config + apiRequest wrapper
│ ├── utils.ts # cn() and other helpers
│ └── constants.ts # App constants
├── App.tsx # Root app component
├── main.tsx # Entry point
└── index.css # Global styles (Tailwind directives)
- Components: PascalCase (e.g.,
SnippetCard.tsx) - Hooks: camelCase starting with
use(e.g.,useSnippets.ts) - Contexts: PascalCase with
Contextsuffix (e.g.,AuthContext.tsx) - Utils: camelCase (e.g.,
utils.ts) - Pages: PascalCase (e.g.,
Snippets.tsx)
// ✅ Good: client/src/components/SnippetCard.tsx
import { type Snippet } from "@shared/schema";
interface SnippetCardProps {
snippet: Snippet;
viewMode: "grid" | "list";
}
export default function SnippetCard({ snippet, viewMode }: SnippetCardProps) {
// Component logic
}// ✅ Good: Always use path aliases
import { Button } from "@/components/ui/button";
import { useAuthContext } from "@/contexts/AuthContext";
import { type Snippet } from "@shared/schema";
import { cn } from "@/lib/utils";
// ❌ Bad: Relative paths across directories
import { Button } from "../../components/ui/button";// ✅ Good: Use cn() from lib/utils.ts
import { cn } from "@/lib/utils";
<div className={cn(
"rounded-lg border p-4",
isActive && "bg-blue-50 dark:bg-blue-950",
className
)} />
// ❌ Bad: Inline styles
<div style={{ borderRadius: "8px", padding: "16px" }} />// ✅ Good: Copy pattern from client/src/pages/Snippets.tsx
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
const { data: snippets } = useQuery({
queryKey: ["/api/snippets"],
queryFn: () => apiRequest<Snippet[]>("/api/snippets")
});
// Mutations
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: InsertSnippet) =>
apiRequest("/api/snippets", { method: "POST", body: data }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["/api/snippets"] })
});// ✅ Good: Copy pattern from client/src/contexts/AuthContext.tsx
import { useAuthContext } from "@/contexts/AuthContext";
function MyComponent() {
const { user, logout } = useAuthContext();
if (!user) return <LoginPrompt />;
return <div>Welcome, {user.displayName}</div>;
}// ✅ Good: Use pre-built Shadcn components from components/ui/
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader } from "@/components/ui/dialog";
import { useToast } from "@/hooks/use-toast";
const { toast } = useToast();
toast({ title: "Success!", description: "Snippet saved." });
// ❌ Bad: Building custom modals from scratch (use Dialog)// ✅ Good: Copy pattern from client/src/lib/firebase.ts
import { auth } from "@/lib/firebase";
import { signInWithEmailAndPassword, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
// Email/password login
const userCredential = await signInWithEmailAndPassword(auth, email, password);
// Google OAuth
const provider = new GoogleAuthProvider();
const result = await signInWithPopup(auth, provider);// ❌ Bad: Legacy class components
class MyComponent extends React.Component {
render() { /* ... */ }
}
// ✅ Good: Functional components
function MyComponent() {
return <div>Content</div>;
}// ❌ Bad: Direct fetch without auth token
const response = await fetch("/api/snippets");
// ✅ Good: Use apiRequest() which handles auth tokens automatically
import { apiRequest } from "@/lib/queryClient";
const data = await apiRequest<Snippet[]>("/api/snippets");- Entry Point:
client/src/main.tsx- App initialization - Root Component:
client/src/App.tsx- Routing and provider setup - Auth Context:
client/src/contexts/AuthContext.tsx- User state and Firebase auth - Query Client:
client/src/lib/queryClient.ts- TanStack Query config + apiRequest wrapper - Firebase Config:
client/src/lib/firebase.ts- Firebase client SDK initialization - Utilities:
client/src/lib/utils.ts- cn() helper for Tailwind classes - Theme Context:
client/src/contexts/ThemeContext.tsx- Dark/light mode - Component Examples:
client/src/components/SnippetCard.tsx- Complex component with mutationsclient/src/components/AddSnippetDialog.tsx- Form with Zod validationclient/src/components/Header.tsx- Layout component with auth state
- AuthContext: User authentication state, login/logout
- ThemeContext: Dark/light mode toggle
- SnippetContext: Snippet CRUD operations (uses TanStack Query internally)
- CollectionContext: Collection management
- CodeThemeContext: Prism.js syntax theme selection
# Find a component
rg -n "export default function" client/src/components
# Find a page
ls client/src/pages/*.tsx
# Find a hook
rg -n "export.*use" client/src/hooks
# Find context usage
rg -n "createContext|useContext" client/src/contexts
# Find all Shadcn UI components
ls client/src/components/ui/
# Find TanStack Query usage
rg -n "useQuery|useMutation" client/src
# Find apiRequest calls
rg -n "apiRequest" client/src
# Check TypeScript errors
npm run check 2>&1 | grep "client/src"# ✅ Good: Exposed to client
VITE_FIREBASE_API_KEY=...
VITE_FIREBASE_PROJECT_ID=...
# ❌ Bad: Not exposed (undefined at runtime)
FIREBASE_API_KEY=...Access in code:
const apiKey = import.meta.env.VITE_FIREBASE_API_KEY;// ✅ Good
import { Button } from "@/components/ui/button";
// ❌ Bad
import { Button } from "../../../components/ui/button";// ✅ Good: Token added automatically
import { apiRequest } from "@/lib/queryClient";
const data = await apiRequest("/api/snippets");
// ❌ Bad: Manual token handling not needed
const token = await user.getIdToken();
fetch("/api/snippets", { headers: { Authorization: `Bearer ${token}` } });// ✅ Good: Import types from shared schema
import { type Snippet, type Collection } from "@shared/schema";
// ❌ Bad: Defining your own types
interface Snippet { /* ... */ }// ✅ Good
import { useToast } from "@/hooks/use-toast";
const { toast } = useToast();
toast({
title: "Success!",
description: "Your changes have been saved.",
});
// ❌ Bad: Using alert() or console.log() for user feedback
alert("Saved!");Before creating a pull request:
# 1. Typecheck passes
npm run check
# 2. Build succeeds
npm run build
# 3. No hardcoded secrets
grep -r "AIza" client/src/ # Example: Check for Firebase keys
# Should only find them in .env references, not hardcoded
# 4. Verify .env.example exists (if you added new env vars)
ls .env.exampleNote: Tests exist in client/src/components/__tests__/ but no test runner is configured in package.json yet.
Example test structure:
client/src/components/
├── SnippetCard.tsx
└── __tests__/
└── SnippetCard.test.tsx
When test runner is added, run:
# (Future) Run client tests
npm run test:client # Not yet implementedFor a complete example of a well-structured component, see:
client/src/components/SnippetCard.tsx
- TypeScript props interface
- Multiple contexts (Auth, Snippet, Toast)
- TanStack Query mutations
- Shadcn UI components (Dialog, DropdownMenu, AlertDialog)
- TailwindCSS styling with cn()
- Conditional rendering based on auth state
- Copy-to-clipboard functionality
- Share link generation
This is the gold standard for component structure in this project.