Project Overview
Flashy is a sophisticated flashcard application built with React, TypeScript, and Supabase that implements a hierarchical deck system with real-time study capabilities. The application combines modern web technologies with proven spaced repetition principles to create an intuitive learning experience. Built on a React + Vite foundation with Tailwind CSS for styling, it features a responsive dashboard with analytics, a Pomodoro timer for focused study sessions, and a robust backend powered by Supabase’s PostgreSQL database.
The system’s architecture emphasizes modularity and type safety, with custom hooks managing state transitions and a service layer abstracting database operations. The hierarchical deck structure allows for complex knowledge organization, while the study mode provides an immersive, distraction-free learning environment.
Problem & Motivation
Traditional flashcard applications often suffer from rigid organizational structures and lack the flexibility needed for complex subject matter. Students and professionals need a system that can handle nested knowledge domains while providing meaningful progress tracking.
Pain Point | Effect |
---|---|
Flat deck structures limit knowledge organization | Difficult to group related concepts and maintain logical study flows |
Lack of study session management | Users struggle to maintain focus and track learning progress effectively |
No visual progress feedback | Motivation drops without clear indicators of improvement |
Poor mobile responsiveness | Learning opportunities are missed when away from desktop environments |
Complex setup and maintenance | Users abandon systems that require extensive configuration |
System Architecture
The application follows a layered architecture pattern with clear separation of concerns:
Frontend Layer: React components with TypeScript for type safety
State Management: Custom hooks (useFlashcards
) managing local state and API calls
Service Layer: Database abstraction layer handling Supabase operations
Data Layer: PostgreSQL database with Row Level Security (RLS) policies
UI Layer: Tailwind CSS with custom design system for consistent theming
The data flow follows: User Action → Component → Hook → Service → Database → Response → State Update → UI Re-render.
Key modules:
- Flashcards Component: Core study interface with card management and navigation
- Dashboard Component: Analytics and progress visualization
- PomodoroTimer Component: Focus management with customizable intervals
- Database Service: CRUD operations with error handling and type safety
- Custom Hooks: State management and business logic encapsulation
Design Choices
- TypeScript-first approach: Ensures type safety across the entire application stack, reducing runtime errors and improving developer experience
- Custom hooks over global state: Localized state management reduces complexity and improves component reusability
- Supabase over custom backend: Leverages PostgreSQL with real-time capabilities while minimizing infrastructure overhead
- Hierarchical deck structure: Enables complex knowledge organization through parent-child relationships
- Responsive design with Tailwind: Ensures consistent experience across devices with utility-first CSS
- Row Level Security: Database-level security policies ensure data isolation and access control
- Local storage for timer state: Persists user preferences and session state across browser sessions
Technical Deep Dive
Database Schema & Type Safety
The application uses a carefully designed PostgreSQL schema with TypeScript types generated from the database structure. The hierarchical deck system is implemented through self-referencing foreign keys.
-- supabase_schema.sql
CREATE TABLE public.decks (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
parent_deck_id bigint REFERENCES public.decks(id) ON DELETE SET NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE TABLE public.cards (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
deck_id bigint NOT NULL REFERENCES public.decks(id) ON DELETE CASCADE,
front_content text NOT NULL,
back_content text NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
The TypeScript types ensure compile-time safety for all database operations:
// src/types/database.types.ts
export type Deck = Database['public']['Tables']['decks']['Row']
export type Card = Database['public']['Tables']['cards']['Row']
export type InsertDeck = Database['public']['Tables']['decks']['Insert']
export type UpdateCard = Database['public']['Tables']['cards']['Update']
State Management with Custom Hooks
The application uses a custom hook pattern for state management, encapsulating business logic and API calls within reusable hooks.
// src/hooks/useFlashcards.ts
export const useFlashcards = () => {
const [decks, setDecks] = useState<Deck[]>([]);
const [currentDeck, setCurrentDeck] = useState<Deck | null>(null);
const [cards, setCards] = useState<Card[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const fetchDeckById = async (deckId: number) => {
setLoading(true);
setError(null);
try {
const deck = await database.getDeckById(deckId);
setCurrentDeck(deck);
if (deck) {
fetchCardsByDeckId(deck.id);
fetchDecks(deck.id); // Fetch sub-decks for the current deck
}
} catch (err) {
setError('Failed to fetch deck');
console.error(err);
} finally {
setLoading(false);
}
};
// ... additional methods for CRUD operations
};
Service Layer Abstraction
The database service layer provides a clean abstraction over Supabase operations, handling error cases and type conversions.
// src/services/database.ts
export const database = {
async createDeck(name: string, parent_deck_id?: number): Promise<Deck> {
const deckData: InsertDeck = {
name,
parent_deck_id: parent_deck_id || null
};
const { data, error } = await supabase
.from('decks')
.insert(deckData)
.select()
.single();
if (error) throw error;
return data;
},
async getCardsByMultipleDeckIds(deck_ids: number[]): Promise<Card[]> {
if (!deck_ids.length) return [];
const { data, error } = await supabase
.from('cards')
.select()
.in('deck_id', deck_ids);
if (error) throw error;
return data || [];
}
};
Study Mode Implementation
The study mode provides an immersive learning experience with card flipping animations and navigation controls.
// src/components/Flashcards.tsx
const initiateStudySession = (studyCards: FlashCard[]) => {
if (studyCards.length > 0) {
setCurrentStudySet(studyCards);
setStudyMode(true);
setCurrentCardIndex(0);
setActiveCard(studyCards[0]);
setShowCardBack(false);
setFlippedCardIds({});
}
setShowStudyOptionsModal(false);
};
const handleFlipCard = () => {
setShowCardBack(!showCardBack);
};
Pomodoro Timer with Persistence
The Pomodoro timer component manages study sessions with customizable intervals and persistent state.
// src/components/PomodoroTimer.tsx
const [settings, setSettings] = useState<TimerSettings>(() => {
const savedSettings = localStorage.getItem('pomodoroSettings');
return savedSettings ? JSON.parse(savedSettings) : {
studyTime: 25,
breakTime: 5,
longBreakTime: 15,
sessionsUntilLongBreak: 4
};
});
useEffect(() => {
let interval: number | undefined;
if (isRunning && timeLeft > 0) {
interval = window.setInterval(() => {
setTimeLeft(time => time - 1);
}, 1000);
} else if (timeLeft === 0) {
const audio = new Audio('https://actions.google.com/sounds/v1/alarms/beep_short.ogg');
audio.play().catch(() => {});
if (!isBreak) {
const newSessionCount = sessionCount + 1;
setSessionCount(newSessionCount);
setIsBreak(true);
if (newSessionCount % settings.sessionsUntilLongBreak === 0) {
setTimeLeft(settings.longBreakTime * 60);
} else {
setTimeLeft(settings.breakTime * 60);
}
} else {
setIsBreak(false);
setTimeLeft(settings.studyTime * 60);
}
}
return () => clearInterval(interval);
}, [isRunning, timeLeft, isBreak, sessionCount, settings]);
User Experience & Performance
The application prioritizes seamless user interactions and optimal study flow:
- Study session tracking: Monitors user engagement and completion rates
- Progress visualization: Charts and analytics help users understand their learning patterns
- Adaptive study options: Users can choose between studying current deck only or including sub-decks
- Session persistence: Timer state and settings are preserved across browser sessions
The focus is on creating an intuitive learning environment that adapts to user preferences and study habits.
Performance Optimizations
The application delivers responsive interactions through careful optimization:
- Optimistic updates: UI updates immediately while background operations complete
- Lazy loading: Cards and decks are loaded on-demand to reduce initial load times
- Efficient queries: Database queries are optimized with proper indexing and selective fetching
- State caching: Frequently accessed data is cached in component state to minimize API calls
The study mode provides instant card flipping and navigation:
Performance optimizations include:
- Debounced search: Prevents excessive API calls during user input
- Virtual scrolling: Handles large card sets efficiently
- Memoized components: Reduces unnecessary re-renders
- Efficient state updates: Batched updates minimize DOM manipulations
What’s Next
The roadmap focuses on enhancing the learning experience and expanding functionality:
- Spaced repetition algorithm: Implement FSRS (Free Spaced Repetition Scheduler) for optimal review scheduling
- Audio support: Add text-to-speech for auditory learners
- Image and media support: Allow flashcards to contain images, diagrams, and audio clips
- Collaborative features: Enable deck sharing and collaborative study sessions
- Advanced analytics: Implement learning curve analysis and retention prediction
- Mobile app: Develop native mobile applications for iOS and Android
- Offline support: Add service worker for offline study capabilities
- AI-powered suggestions: Use machine learning to suggest optimal study times and content organization
The application is designed with extensibility in mind, making these enhancements achievable through the existing modular architecture.
Last updated on August 24, 2025 at 12:16 PM EST. See Changelog