diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f6a94ed --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,237 @@ +# Contributing to OnePulse + +Thank you for your interest in contributing to OnePulse! This guide will help you understand how to contribute effectively to the project. + +## Getting Started + +1. **Fork the repository** on GitHub +2. **Clone your fork** locally +3. **Install dependencies** with `npm install` +4. **Create a new branch** for your feature/fix: `git checkout -b feature/your-feature-name` + +## Development Setup + +```bash +# Install dependencies +npm install + +# Run the development server +npm run dev + +# Check code quality +npm run check + +# Automatically fix code issues +npm run fix + +# Type checking +npm run typecheck +``` + +## Code Standards + +This project follows the **Ultracite** (Biome preset) code standards. Key principles: + +### Style & Formatting + +- **Automatic formatting**: Run `npm run fix` before committing +- **Type safety**: Always provide explicit types when enhancing clarity +- **Self-documenting code**: Write clear variable/function names, use JSDoc for complex logic +- **Accessibility**: Include ARIA labels, semantic HTML, and proper keyboard support + +### Architecture Guidelines + +#### Component Structure + +- Use functional components with React 19+ patterns +- Leverage `use*` hooks for complex logic separation +- Place components in meaningful subdirectories +- Keep components focused on a single responsibility + +#### Hooks + +- Extract complex component logic into custom hooks +- Hook files should start with `use-` prefix +- Use `readonly` for props to prevent mutations +- Return explicit types from hooks + +#### Error Handling + +- Use the `handleError` function from `@/lib/error-handling` +- Provide user-friendly error messages via `ERROR_MESSAGES` constants +- Log errors to stderr for debugging +- Use try-catch in async operations + +#### Validation + +- Use validation utilities from `@/lib/validation` +- Always validate user input, especially addresses and numeric values +- Use type guards for runtime safety +- Never trust client-side data implicitly + +### TypeScript + +- Use strict mode (enforced in tsconfig) +- Prefer explicit over implicit types +- Use `unknown` instead of `any` +- Create meaningful type aliases for complex structures +- Export types alongside implementations + +### React & JSX + +- Use `"use client"` directive in client components +- Call hooks at the top level (never conditionally) +- Specify stable `key` props in lists (prefer unique IDs over indices) +- Use semantic HTML and ARIA attributes +- Memoize expensive computations with `useMemo` +- Prevent unnecessary renders with `React.memo` + +### Performance + +- Lazy load components using `React.lazy` +- Use images efficiently with `next/image` +- Avoid inline function definitions in props +- Batch state updates when possible +- Profile with React DevTools before optimizing + +### Security + +- **Never hardcode secrets** - use environment variables +- **Validate all inputs** - especially addresses and contract calls +- **Prevent XSS** - use `.textContent` instead of `.innerHTML` +- **Avoid eval()** and dynamic code execution +- **Use parameterized queries** for database operations +- **Add SSRF protection** when handling user-provided URLs + +## Commit Guidelines + +```bash +# Use descriptive, lowercase commit messages +git commit -m "feat: add validation utilities for ethereum addresses" +git commit -m "fix: improve error handling in gm-chain-card component" +git commit -m "docs: add JSDoc comments to countdown component" +git commit -m "refactor: extract chain logic into custom hook" +``` + +### Commit Message Format + +- **feat**: A new feature +- **fix**: A bug fix +- **docs**: Documentation changes +- **refactor**: Code refactoring without feature/fix +- **perf**: Performance improvements +- **test**: Adding or updating tests +- **chore**: Dependency updates or tooling changes + +## Pull Request Process + +1. **Push to your fork** + ```bash + git push origin feature/your-feature-name + ``` + +2. **Create a Pull Request** with: + - Clear title describing the change + - Detailed description of what changed and why + - Reference any related issues (e.g., `Closes #123`) + - Screenshots for UI changes + +3. **Ensure all checks pass**: + - ✅ Code formatting (`npm run check`) + - ✅ TypeScript compilation (`npm run typecheck`) + - ✅ No console errors or warnings + +4. **Request review** from maintainers + +5. **Respond to feedback** and make requested changes + +## What to Contribute + +### Great First Issues + +- Improving error messages +- Adding JSDoc comments to existing functions +- Creating utility functions with tests +- Fixing accessibility issues +- Improving TypeScript types + +### Feature Suggestions + +- New validation utilities +- Enhanced error handling +- Performance optimizations +- Better component composition +- Additional configuration options + +### Bug Fixes + +- Document the bug clearly +- Include steps to reproduce if possible +- Link to any relevant error logs +- Test your fix thoroughly + +## Testing + +While we don't have formal tests yet, ensure: + +1. **Manual testing** in development mode +2. **Cross-browser testing** (Chrome, Firefox, Safari) +3. **Mobile testing** (Farcaster Mini App context) +4. **Type checking** passes without errors +5. **Code quality** passes linting + +## Documentation + +- Add JSDoc comments to all exported functions +- Update README.md for significant changes +- Include inline comments for non-obvious logic +- Document any new environment variables +- Provide examples for complex features + +## Project Structure Reference + +``` +app/ # Next.js app router and pages +├── api/ # API routes +├── layout.tsx # Root layout +└── page.tsx # Home page + +components/ # React components +├── gm-chain-card/ # Per-chain GM functionality +├── providers/ # Context providers +└── ui/ # Reusable UI components + +lib/ # Utilities and helpers +├── constants.ts # Configuration constants +├── error-handling.ts # Error handling utilities +├── utils.ts # General utilities +└── validation.ts # Input validation + +types/ # TypeScript type definitions +hooks/ # Custom React hooks +``` + +## Environment Variables + +Required environment variables: +- None yet (configured via contracts and constants) + +Optional for development: +- `NEXT_PUBLIC_*` - Exposed to browser + +## Questions? + +- Check existing issues and discussions +- Review the codebase and inline comments +- Reach out to maintainers on Farcaster (@pirosb3, @linda, @deodad) + +## Code of Conduct + +- Be respectful and inclusive +- Provide constructive feedback +- Focus on the code, not the person +- Help others learn and grow + +--- + +Thank you for contributing to OnePulse! 🚀 diff --git a/components/countdown.tsx b/components/countdown.tsx index 3d52acd..904ce0e 100644 --- a/components/countdown.tsx +++ b/components/countdown.tsx @@ -1,17 +1,24 @@ import { ClockIcon } from "lucide-react"; import { useCountdown } from "@/hooks/use-countdown"; +/** + * Countdown component that displays time until the next GM reset + * Shows a live countdown in HH:MM:SS format + * Uses a pill-shaped design with hover effects + * + * @returns The rendered countdown component + */ export function Countdown() { const { text } = useCountdown(); return (
- +
diff --git a/components/header.tsx b/components/header.tsx index 5f1cb22..2c5a285 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -5,8 +5,16 @@ import { useHeaderLogic } from "@/components/header/use-header-logic"; import { UserInfo } from "@/components/user-info"; import { minikitConfig } from "@/minikit.config"; +/** + * Header component for OnePulse + * Displays the app name when not connected and user info when connected + * Fixed at the top of the viewport with z-index 20 + * + * @returns The rendered header component + */ export function Header(): JSX.Element { const { address, shouldShowUserInfo } = useHeaderLogic(); + return (
diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..cd57ad4 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,24 @@ +/** + * Central export point for lib utilities + * Provides organized access to all utility functions and types + */ + +// Error handling and validation +export * from "./error-handling"; +export * from "./validation"; + +// Utility functions +export * from "./utils"; +export * from "./constants"; + +// Type utilities +export type { SupportedChain, ChainId } from "./constants"; + +// ENS utilities +export * from "./ens-utils"; + +// Smart contract utilities +export * from "./contracts"; + +// Font utilities +export * from "./fonts"; diff --git a/lib/utils.ts b/lib/utils.ts index 894093d..434d988 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -72,3 +72,57 @@ export function isSponsoredOnChain(sponsored: boolean): boolean { export function getChainIconName(): string { return "base"; } + +/** + * Format a number with a specific number of decimal places + */ +export function formatNumber(value: number, decimals: number = 2): string { + return value.toLocaleString("en-US", { + minimumFractionDigits: decimals, + maximumFractionDigits: decimals, + }); +} + +/** + * Truncate an Ethereum address to display format (0x1234...5678) + */ +export function truncateAddress(address: string, chars: number = 4): string { + if (!address) return ""; + if (address.length <= chars * 2 + 2) return address; + return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`; +} + +/** + * Wait for a specified duration (in milliseconds) + * Useful for rate limiting and testing + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +/** + * Create a memoized version of a computation result + * Only recomputes if dependencies change + */ +export function memoize( + computeFn: () => T, + dependencies: readonly unknown[] +): { + current: T; + dependencies: readonly unknown[]; +} { + return { + current: computeFn(), + dependencies, + }; +} + +/** + * Check if two arrays are equal (shallow comparison) + */ +export function arraysEqual(a: T[], b: T[]): boolean { + if (a.length !== b.length) return false; + return a.every((item, index) => item === b[index]); +} diff --git a/lib/validation.ts b/lib/validation.ts new file mode 100644 index 0000000..fe19df1 --- /dev/null +++ b/lib/validation.ts @@ -0,0 +1,94 @@ +/** + * Input validation utilities for OnePulse + * Provides secure and reusable validation functions for common data types + */ + +/** + * Validates if a string is a valid Ethereum address (0x-prefixed 40 hex characters) + */ +export function isValidAddress(value: unknown): value is `0x${string}` { + if (typeof value !== "string") { + return false; + } + return /^0x[0-9a-fA-F]{40}$/.test(value); +} + +/** + * Validates if a value is a non-negative integer + */ +export function isNonNegativeInteger(value: unknown): value is number { + return Number.isInteger(value) && value >= 0; +} + +/** + * Validates if a string is a valid numeric string (can be parsed to a number) + */ +export function isValidNumericString(value: unknown): value is string { + if (typeof value !== "string") { + return false; + } + const parsed = Number.parseFloat(value); + return !Number.isNaN(parsed) && value.trim() !== ""; +} + +/** + * Safely parses a numeric string to a number + * Returns null if parsing fails + */ +export function safeParseNumber(value: string): number | null { + const parsed = Number.parseFloat(value); + return Number.isNaN(parsed) ? null : parsed; +} + +/** + * Validates if a timestamp (in seconds) is reasonable (not in the distant future) + * Allows up to 1 year in the future as a reasonable buffer + */ +export function isValidTimestamp( + timestampSeconds: number, + maxFutureSeconds: number = 365 * 24 * 60 * 60 +): boolean { + const now = Math.floor(Date.now() / 1000); + const oneSecondAgo = now - 1; + const maxAllowed = now + maxFutureSeconds; + + return ( + isNonNegativeInteger(timestampSeconds) && + timestampSeconds >= oneSecondAgo && + timestampSeconds <= maxAllowed + ); +} + +/** + * Validates if a value is a supported chain ID + */ +export function isValidChainId(value: unknown): value is number { + return Number.isInteger(value) && value > 0; +} + +/** + * Type guard for checking if an error is an Error instance + */ +export function isErrorInstance(error: unknown): error is Error { + return error instanceof Error; +} + +/** + * Safely extracts message from an error object + * Returns the error message if available, otherwise returns a default message + */ +export function getErrorMessage(error: unknown, defaultMessage = "Unknown error"): string { + if (isErrorInstance(error)) { + return error.message; + } + if (typeof error === "string") { + return error; + } + if (typeof error === "object" && error !== null && "message" in error) { + const message = (error as Record).message; + if (typeof message === "string") { + return message; + } + } + return defaultMessage; +} diff --git a/types/transaction.ts b/types/transaction.ts index f506788..e57a436 100644 --- a/types/transaction.ts +++ b/types/transaction.ts @@ -1 +1,32 @@ export type TransactionStatus = "default" | "success" | "error" | "pending"; + +/** + * Represents the result of a transaction operation + */ +export type TransactionResult = { + success: boolean; + status: TransactionStatus; + data?: T; + error?: Error | null; + hash?: `0x${string}`; +}; + +/** + * Represents wallet connection state + */ +export type WalletState = { + isConnected: boolean; + address?: `0x${string}`; + chainId?: number; + isConnecting?: boolean; +}; + +/** + * Represents user activity on a chain + */ +export type ChainActivity = { + chainId: number; + lastActivityTime: number; + hasClaimedToday: boolean; + consecutiveDays: number; +};