Welcome to the DevSphere Frontend project! This guide will help you understand our conventions, patterns, and best practices for contributing to this React/TypeScript application.
- Getting Started
- Project Structure
- Naming Conventions
- Component Conventions
- Page Components
- Layout Components
- UI Components
- Routing & Navigation
- State Management
- Authentication
- Styling Guidelines
- Type Definitions
- Code Style Guidelines
- Node.js (v18+)
- npm or yarn package manager
- Backend server running on
http://localhost:3000
- Clone the repository
- Install dependencies:
npm install - Start development server:
npm run dev - Open browser to
http://localhost:5173
src/
├── components/ # Reusable UI components and layouts
│ ├── ui/ # Shadcn/ui components (Button, Input, etc.)
│ ├── UserLayout.tsx # Layout wrapper for user pages
│ ├── AdminLayout.tsx # Layout wrapper for admin pages
│ ├── UserNavbar.tsx # User navigation component
│ └── AdminSidebar.tsx# Admin sidebar component
├── pages/ # Page components organized by user type
│ ├── user/ # User-facing pages
│ └── admin/ # Admin panel pages
├── lib/ # Utility libraries and configurations
│ ├── auth-client.ts # Authentication client setup
│ └── utils.ts # Common utility functions
├── App.tsx # Main app component with routing
├── main.tsx # Application entry point
├── index.css # Global styles and Tailwind imports
└── vite-env.d.ts # Vite type declarations
- Use PascalCase for React component files:
UserHome.tsx,AdminLogin.tsx - Use camelCase for utility files:
auth-client.ts,utils.ts - Use kebab-case for configuration files:
vite.config.ts,tailwind.config.js
- Use PascalCase for component names:
UserNavbar,AdminLayout - Use camelCase for component instances and variables:
userHome,adminDashboard - Use descriptive names that indicate purpose:
UserLayout,AdminSidebar
- Use camelCase for directories:
components/,pages/ - Group related components:
pages/user/,pages/admin/ - Use
ui/for reusable UI components
ALL components should be functional components using modern React patterns:
import React from 'react';
const ExampleComponent = () => {
return (
<div>
<h1>Example Component</h1>
</div>
);
};
export default ExampleComponent;import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { someUtility } from '@/lib/utils';
import type { ExampleProps } from '@/types/example.types';
interface ComponentProps {
title: string;
optional?: boolean;
}
const ExampleComponent: React.FC<ComponentProps> = ({
title,
optional = false
}) => {
// 1. Hooks (useState, useEffect, custom hooks)
const [isLoading, setIsLoading] = useState(false);
// 2. Event handlers
const handleClick = () => {
// Handle click logic
};
// 3. Effects
useEffect(() => {
// Effect logic
}, []);
// 4. Early returns for loading/error states
if (isLoading) {
return <div>Loading...</div>;
}
// 5. Main render
return (
<div className="container">
<h1>{title}</h1>
<Button onClick={handleClick}>
Click me
</Button>
</div>
);
};
export default ExampleComponent;Always define props interfaces within the component file:
interface UserCardProps {
name: string;
email: string;
role?: 'admin' | 'user';
onEdit?: (id: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({ name, email, role = 'user', onEdit }) => {
// Component implementation
};Page components represent full pages and follow specific conventions.
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { authClient } from '@/lib/auth-client';
const UserHome = () => {
const navigate = useNavigate();
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Fetch data on component mount
fetchData();
}, []);
const fetchData = async () => {
try {
setIsLoading(true);
// API call logic
} catch (error) {
console.error('Failed to fetch data:', error);
} finally {
setIsLoading(false);
}
};
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">User Home</h1>
{/* Page content */}
</div>
);
};
export default UserHome;- User pages: Located in
src/pages/user/ - Admin pages: Located in
src/pages/admin/ - Each page should be a default export
- Use descriptive names:
UserHome,AdminDashboard,AdminLogin
Layout components wrap pages and provide consistent navigation and structure.
import React from 'react';
import { Outlet } from 'react-router-dom';
import Navbar from './UserNavbar';
const UserLayout = () => {
return (
<div className="min-h-screen bg-background">
<Navbar />
<main className="container mx-auto">
<Outlet />
</main>
</div>
);
};
export default UserLayout;- Use
<Outlet />for nested route rendering - Include navigation components (navbar, sidebar)
- Apply global layout styles and containers
- Handle responsive design at layout level
UI components are located in src/components/ui/ and follow Shadcn/ui patterns.
Routes are configured in App.tsx:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import UserLayout from '@/components/UserLayout';
import AdminLayout from '@/components/AdminLayout';
const App = () => {
return (
<BrowserRouter>
<Routes>
{/* User Routes */}
<Route path="/" element={<UserLayout />}>
<Route index element={<UserHome />} />
<Route path="events" element={<UserEvents />} />
<Route path="members" element={<UserMembers />} />
<Route path="projects" element={<UserProjects />} />
</Route>
{/* Admin Routes */}
<Route path="/admin" element={<AdminLayout />}>
<Route index element={<AdminDashboard />} />
<Route path="login" element={<AdminLogin />} />
<Route path="events" element={<AdminEvents />} />
<Route path="members" element={<AdminMembers />} />
<Route path="projects" element={<AdminProjects />} />
</Route>
</Routes>
</BrowserRouter>
);
};- Use React Router's
useNavigatefor programmatic navigation - Use
Linkcomponent for declarative navigation - Implement route guards for protected routes
import { useNavigate, Link } from 'react-router-dom';
const Component = () => {
const navigate = useNavigate();
const handleNavigation = () => {
navigate('/admin/dashboard');
};
return (
<div>
<Link to="/events">Events</Link>
<button onClick={handleNavigation}>Go to Admin</button>
</div>
);
};Use useState for component-level state:
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState<User | null>(null);
const [formData, setFormData] = useState({
name: '',
email: '',
});Use controlled components for forms:
const [formData, setFormData] = useState({
email: '',
password: '',
});
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
return (
<Input
name="email"
value={formData.email}
onChange={handleInputChange}
/>
);Authentication is handled using Better Auth client from @/lib/auth-client.
import { authClient } from '@/lib/auth-client';
const LoginComponent = () => {
const { signIn, signUp, useSession } = authClient;
const handleLogin = async (email: string, password: string) => {
try {
const response = await signIn.email({ email, password });
// Handle successful login
} catch (error) {
console.error('Login failed:', error);
}
};
return (
// Component JSX
);
};import { useSession } from '@/lib/auth-client';
const ProtectedComponent = () => {
const session = useSession();
if (!session) {
return <div>Please log in</div>;
}
return <div>Welcome, {session.user.name}!</div>;
};The project uses Tailwind CSS for styling. Follow these conventions:
// Group classes logically
<div className="
flex items-center justify-center
w-full max-w-md
bg-white border border-gray-200 rounded-lg
p-6 shadow-lg
"><div className="
text-sm md:text-base lg:text-lg
p-4 md:p-6 lg:p-8
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
">Use CSS variables defined in src/index.css:
.custom-component {
background-color: var(--color-primary);
color: var(--color-primary-foreground);
}- Use
cn()utility from@/lib/utilsfor conditional classes - Prefer Tailwind classes over custom CSS
- Use design system tokens for consistency
import { cn } from '@/lib/utils';
const Button = ({ variant, className, ...props }) => {
return (
<button
className={cn(
'base-button-classes',
{
'primary-variant': variant === 'primary',
'secondary-variant': variant === 'secondary',
},
className
)}
{...props}
/>
);
};- Define interfaces for props, state, and API responses
- Use proper typing for event handlers
- Leverage TypeScript's strict mode
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user' | 'moderator';
createdAt: Date;
}
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
type EventHandler<T = HTMLButtonElement> = (
event: React.MouseEvent<T>
) => void;Use type imports for type-only imports:
import type { User } from '@/types/user';
import type { ComponentProps } from 'react';- React and React-related imports
- Third-party library imports
- Internal utility imports
- Component imports
- Type imports
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import type { User } from '@/types/user';Use consistent error handling patterns:
const fetchData = async () => {
try {
setIsLoading(true);
const response = await api.getData();
setData(response.data);
} catch (error) {
console.error('Failed to fetch data:', error);
setError('Failed to load data. Please try again.');
} finally {
setIsLoading(false);
}
};Use descriptive names and proper typing:
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Handle form submission
};
const handleUserClick = (userId: string) => {
// Handle user click
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// Handle input change
};- Access via
import.meta.env(Vite convention) - Prefix with
VITE_for client-side access - Define types for environment variables
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';import { render, screen, fireEvent } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import UserHome from '@/pages/user/UserHome';
const renderWithRouter = (component: React.ReactElement) => {
return render(
<BrowserRouter>
{component}
</BrowserRouter>
);
};
describe('UserHome', () => {
it('renders user home page', () => {
renderWithRouter(<UserHome />);
expect(screen.getByText('UserHome')).toBeInTheDocument();
});
});- Create feature branches from
main - Follow naming conventions outlined in this guide
- Test your changes thoroughly
- Ensure TypeScript compilation passes
- Update documentation if needed
- Run linting before submitting
type(scope): description
Examples:
feat(auth): add login functionality
fix(ui): resolve button styling issue
docs: update component documentation
refactor(layout): improve responsive design
style: format code with prettier
feature/user-authentication
fix/navbar-responsive-issue
refactor/component-structure
docs/update-contributing-guide
# Development server
npm run dev
# Build for production
npm run build
# Run linting
npm run lint
# Preview production build
npm run preview
# Type checking
npx tsc --noEmitIf you have questions about these conventions or need clarification on any patterns, please:
- Check existing code in the repository for examples
- Review the README.md for project overview
- Create an issue for discussion
- Ask in team communication channels
Thank you for contributing to DevSphere Frontend!