Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# API Documentation

## Endpoints

### GET /api/questions

Fetch questions for a game session.

**Query Parameters:**
- `category` (optional): Filter by category
- `difficulty` (optional): easy, medium, hard, expert
- `limit` (optional): Number of questions (default: 10)

**Response:**
```json
{
"questions": [
{
"id": "q1",
"question": "What is the capital of France?",
"options": ["Paris", "London", "Berlin", "Madrid"],
"correctAnswer": 0,
"category": "general",
"difficulty": "easy"
}
]
}
```

### POST /api/scores

Submit game score.

**Body:**
```json
{
"address": "0x...",
"score": 8500,
"category": "science",
"difficulty": "medium"
}
```

### GET /api/leaderboard

Get top scores.

**Query Parameters:**
- `limit` (optional): Number of entries (default: 10)
- `category` (optional): Filter by category
51 changes: 51 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Deployment Guide

## Prerequisites

- Node.js 18+
- npm or yarn
- Git
- Vercel account (for frontend)
- Alchemy/Infura account (for Base)
- The Graph account (for indexing)

## Environment Variables

```env
NEXT_PUBLIC_RPC_URL=your_base_rpc_url
NEXT_PUBLIC_CONTRACT_ADDRESS=your_contract_address
NEXT_PUBLIC_GRAPH_URL=your_subgraph_url
```

## Frontend Deployment

### Vercel (Recommended)

```bash
npm i -g vercel
vercel
```

### Build for Production

```bash
npm run build
npm start
```

## Smart Contract Deployment

### Using Foundry

```bash
cd contracts
forge build
forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY src/TriviaGame.sol:TriviaGame
```

## Subgraph Deployment

```bash
cd subgraph
graph deploy --product hosted-service your-username/reui
```
30 changes: 30 additions & 0 deletions PERFORMANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Performance Optimization Guide

## Frontend

### Code Splitting
- Use dynamic imports for routes
- Lazy load heavy components
- Implement virtualization for lists

### Caching
- Use SWR or React Query for data fetching
- Implement proper cache strategies
- Use service workers for offline support

### Bundle Optimization
- Analyze bundle with next/bundle-analyzer
- Remove unused code
- Compress assets

## Backend

### Database
- Index frequently queried fields
- Use pagination for large datasets
- Implement caching layer

### API
- Rate limiting
- Request validation
- Response compression
25 changes: 25 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Security Best Practices

## Smart Contracts Open

- UseZeppelin libraries
- Implement access control
- Validate all inputs
- Use SafeMath for arithmetic
- Emit events for all state changes

## Frontend

- Never expose private keys
- Validate user inputs
- Use HTTPS only
- Implement rate limiting
- Sanitize user-generated content

## General

- Keep dependencies updated
- Use environment variables for secrets
- Implement proper error handling
- Log security events
- Regular security audits
26 changes: 26 additions & 0 deletions frontend/src/constants/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface Theme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
}

export const lightTheme: Theme = {
colors: {
primary: '#8B5CF6',
secondary: '#EC4899',
background: '#FFFFFF',
text: '#1F2937',
},
};

export const darkTheme: Theme = {
colors: {
primary: '#8B5CF6',
secondary: '#EC4899',
background: '#1F2937',
text: '#FFFFFF',
},
};
12 changes: 12 additions & 0 deletions frontend/src/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useState, useEffect } from 'react';

export const useDebounce = <T>(value: T, delay: number = 500): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);

return debouncedValue;
};
16 changes: 16 additions & 0 deletions frontend/src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState, useEffect } from 'react';

export const useLocalStorage = <T>(key: string, initialValue: T): [T, (value: T) => void] => {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});

const setValue = (value: T) => {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
};

return [storedValue, setValue];
};
19 changes: 19 additions & 0 deletions frontend/src/lib/apiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ApiResponse, PaginatedResponse } from '@/types/apiResponse';

const API_BASE = process.env.NEXT_PUBLIC_API_URL || '/api';

export const apiClient = {
async get<T>(endpoint: string): Promise<ApiResponse<T>> {
const res = await fetch(`${API_BASE}${endpoint}`);
return res.json();
},

async post<T>(endpoint: string, data: any): Promise<ApiResponse<T>> {
const res = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return res.json();
},
};
15 changes: 15 additions & 0 deletions frontend/src/lib/apiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createError } from './errorUtils';

export class ApiError extends Error {
constructor(message: string, public status: number = 500) {
super(message);
this.name = 'ApiError';
}
}

export const handleApiError = (error: any): never => {
if (error.response) {
throw new ApiError(error.response.data.message, error.response.status);
}
throw new ApiError(error.message || 'Network error');
};
13 changes: 13 additions & 0 deletions frontend/src/lib/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface EnvConfig {
rpcUrl: string;
contractAddress: string;
graphUrl: string;
network: 'base-mainnet' | 'base-sepolia';
}

export const getEnvConfig = (): EnvConfig => ({
rpcUrl: process.env.NEXT_PUBLIC_RPC_URL || '',
contractAddress: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS || '',
graphUrl: process.env.NEXT_PUBLIC_GRAPH_URL || '',
network: (process.env.NEXT_PUBLIC_NETWORK as EnvConfig['network']) || 'base-sepolia',
});
11 changes: 11 additions & 0 deletions frontend/src/lib/validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const validateAddress = (address: string): boolean => {
return /^0x[a-fA-F0-9]{40}$/.test(address);
};

export const validateEmail = (email: string): boolean => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};

export const validateScore = (score: number): boolean => {
return score >= 0 && score <= 1000000;
};
13 changes: 13 additions & 0 deletions frontend/src/types/apiResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}

export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
limit: number;
hasMore: boolean;
}
17 changes: 17 additions & 0 deletions frontend/src/utils/asyncUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const delay = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));

export const retry = async <T>(fn: () => Promise<T>, retries = 3, delayMs = 1000): Promise<T> => {
try {
return await fn();
} catch (error) {
if (retries <= 0) throw error;
await delay(delayMs);
return retry(fn, retries - 1, delayMs * 2);
}
};

export const chunk = <T>(array: T[], size: number): T[][] => {
return Array.from({ length: Math.ceil(array.length / size) }, (_, i) =>
array.slice(i * size, i * size + size)
);
};
12 changes: 12 additions & 0 deletions frontend/src/utils/formatters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const formatAddress = (address: string): string => {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};

export const formatNumber = (num: number): string => {
return new Intl.NumberFormat().format(num);
};

export const formatCurrency = (amount: number, currency = 'USD'): string => {
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);
};
12 changes: 12 additions & 0 deletions frontend/src/utils/stringUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const truncateText = (text: string, maxLength: number): string => {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...';
};

export const capitalize = (str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};

export const slugify = (text: string): string => {
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
};
Loading