Skip to content

10 - Coding Standards

Overview

This document outlines the coding standards and best practices for the Wavic WebApp project.

TypeScript

Type Definitions

Use explicit types for all function parameters and return values:

typescript
// ✅ Good
function getArtist(id: string): Promise<TArtist> {
  // ...
}

// ❌ Avoid
function getArtist(id) {
  // ...
}

Type Naming Conventions

PatternUsageExample
T prefixType aliasesTArtist, TTrack
I prefixInterfacesIUser, IProject
PascalCaseEnumsTrackStatus, LAYOUT_OPTIONS
No prefixPropsArtistCardProps

Avoid any

typescript
// ✅ Good
const data: TArtist[] = await response.json();

// ❌ Avoid
const data: any = await response.json();

Use Type Guards

typescript
function isTrack(item: TArtist | TTrack): item is TTrack {
  return 'audio' in item;
}

React Components

Component Structure

typescript
'use client'; // Only if needed

// 1. Imports
import { useState, useEffect } from 'react';
import cn from '@/utils/class-names';
import { TArtist } from 'types/artist';

// 2. Types/Interfaces
interface ArtistCardProps {
  artist: TArtist;
  onSelect?: (artist: TArtist) => void;
  className?: string;
}

// 3. Component
export default function ArtistCard({
  artist,
  onSelect,
  className,
}: ArtistCardProps) {
  // 4. Hooks
  const [isHovered, setIsHovered] = useState(false);

  // 5. Effects
  useEffect(() => {
    // ...
  }, []);

  // 6. Handlers
  const handleClick = () => {
    onSelect?.(artist);
  };

  // 7. Render
  return (
    <div
      className={cn('p-4 rounded-lg', className)}
      onClick={handleClick}
    >
      {artist.name}
    </div>
  );
}

Client vs Server Components

typescript
// Server Component (default) - No 'use client'
// Can: fetch data, access backend, use async/await
// Cannot: useState, useEffect, browser APIs

// Client Component
'use client';
// Can: useState, useEffect, event handlers, browser APIs
// Cannot: async component, direct server-side operations

Naming Conventions

TypeConventionExample
ComponentsPascalCaseArtistCard.tsx
HookscamelCase with useuseWindowSize.ts
UtilitiescamelCaseformatDate.ts
ConstantsSCREAMING_SNAKEMAX_FILE_SIZE
Fileskebab-caseartist-card.tsx

Styling

Tailwind CSS

Use Tailwind utility classes:

typescript
// ✅ Good
<div className="flex items-center gap-4 p-4 bg-white rounded-lg" />

// ❌ Avoid inline styles
<div style={{ display: 'flex', alignItems: 'center' }} />

Class Name Utility

Use cn() for conditional classes:

typescript
import cn from '@/utils/class-names';

<div className={cn(
  'base-classes',
  isActive && 'active-classes',
  variant === 'primary' && 'primary-classes',
  className // Allow override
)} />

Responsive Design

Mobile-first approach:

typescript
<div className={cn(
  'text-sm',        // Mobile
  'md:text-base',   // Tablet
  'lg:text-lg',     // Desktop
)} />

Dark Mode

Use Tailwind dark variant:

typescript
<div className="bg-white dark:bg-gray-900 text-black dark:text-white" />

State Management

Local State

Use for component-specific UI state:

typescript
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState('');

Context

Use for feature-level shared state:

typescript
// Creating context
const WaveContext = createContext<WaveContextProps | null>(null);

// Using context
const { track, setTrack } = useWaveContext();

Server State

Use Server Actions for data fetching:

typescript
'use server';

export async function getArtists(token: string) {
  const response = await fetch(`${API_URL}/artist`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  return response.json();
}

API Calls

Standard Pattern

typescript
export async function fetchData(token: string): Promise<DataType> {
  try {
    const response = await fetch(`${API_URL}/endpoint`, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

Error Handling

typescript
// In components
try {
  const data = await fetchData(token);
  setData(data);
} catch (error) {
  toast.error('Failed to load data');
  setError(error);
}

File Organization

Imports Order

typescript
// 1. React/Next.js
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';

// 2. Third-party libraries
import { toast } from 'react-hot-toast';
import { motion } from 'framer-motion';

// 3. Internal - absolute imports
import { routes } from '@/config/routes';
import cn from '@/utils/class-names';
import { useWaveContext } from '@/contexts/waveContext';

// 4. Internal - types
import { TArtist } from 'types/artist';

// 5. Internal - relative imports
import { ChildComponent } from './child-component';

Export Patterns

typescript
// Named exports for utilities
export function formatDate(date: Date) {}
export const MAX_SIZE = 1024;

// Default export for components
export default function MyComponent() {}

Error Handling

Try-Catch

typescript
async function handleSubmit() {
  try {
    setLoading(true);
    await createArtist(formData, token);
    toast.success('Artist created');
    router.push('/');
  } catch (error) {
    toast.error('Failed to create artist');
    console.error(error);
  } finally {
    setLoading(false);
  }
}

Error Boundaries

Wrap feature areas with error boundaries:

typescript
<ErrorBoundary fallback={<ErrorFallback />}>
  <FeatureComponent />
</ErrorBoundary>

Performance

Memoization

typescript
// Expensive computations
const sortedTracks = useMemo(
  () => tracks.sort((a, b) => a.order - b.order),
  [tracks]
);

// Stable callbacks
const handleClick = useCallback(() => {
  setIsOpen(true);
}, []);

Lazy Loading

typescript
import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(
  () => import('./heavy-component'),
  { loading: () => <Skeleton /> }
);

Image Optimization

typescript
import Image from 'next/image';

<Image
  src={artist.image}
  alt={artist.name}
  width={200}
  height={200}
  placeholder="blur"
  blurDataURL={blurHash}
/>

Testing (Future)

Component Testing

typescript
// __tests__/artist-card.test.tsx
import { render, screen } from '@testing-library/react';
import ArtistCard from '../artist-card';

describe('ArtistCard', () => {
  it('renders artist name', () => {
    render(<ArtistCard artist={mockArtist} />);
    expect(screen.getByText(mockArtist.name)).toBeInTheDocument();
  });
});

Documentation

JSDoc Comments

typescript
/**
 * Fetches artist data from the API
 * @param token - JWT authentication token
 * @param artistId - The artist's unique identifier
 * @returns Promise resolving to artist data
 * @throws Error if the request fails
 */
export async function getArtist(
  token: string,
  artistId: string
): Promise<TArtist> {
  // ...
}

Component Documentation

typescript
/**
 * ArtistCard displays an artist's information in a card format.
 *
 * @example
 * ```tsx
 * <ArtistCard
 *   artist={artist}
 *   onSelect={handleSelect}
 *   className="mb-4"
 * />
 * ```
 */
export default function ArtistCard({ ... }) { }

Git Practices

Commit Messages

<type>(<scope>): <description>

feat(artist): add artist card component
fix(player): resolve volume slider issue
docs(readme): update installation steps
style(ui): format button component
refactor(auth): simplify login flow
test(artist): add unit tests for artist service
chore(deps): update dependencies

Branch Naming

feature/add-artist-sharing
fix/player-volume-bug
docs/update-readme
refactor/auth-simplification

Ctrl-Audio Platform Documentation