A comprehensive authentication system built with Next.js, Firebase, and React that protects routes and manages user sessions.
-
Install dependencies
npm install # or bun install -
Set up environment variables
# Firebase Configuration NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_storage_bucket NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=your_measurement_id # Firebase Admin (for server-side operations) FIREBASE_SERVICE_ACCOUNT_KEY=your_service_account_key_json
-
Run the development server
npm run dev # or bun dev
The authentication system provides double-layer protection:
- Middleware Level: Server-side route protection
- Component Level: Client-side authentication checks
User Request โ Middleware Check โ Protected Route Component โ Page Content
โ โ โ โ
Has Token? Valid Token? Authenticated? Show Content
โ โ โ โ
No Invalid/Expired No Redirect to Login
โ โ โ โ
Redirect to Login Page
The habit streak system is a real-time, dynamic tracking mechanism that automatically calculates and maintains user consistency over time. It's NOT hardcoded and provides genuine streak accumulation capabilities.
- Dynamic Calculation: Streaks are calculated in real-time based on actual completion dates
- Consecutive Days: Only counts consecutive days (breaks if a day is missed)
- Automatic Updates: Streak recalculates every time a habit is ticked/un-ticked
Week 1: Complete 7 days โ Streak = 7 days
Week 2: Complete next 7 days โ Streak = 14 days
Week 3: Complete next 7 days โ Streak = 21 days
Week 4: Complete next 7 days โ Streak = 28 days
- Always Available: Tick option is available for every day
- Date Flexibility: Can tick any date (not just today)
- Streak Maintenance: Users can maintain streaks by ticking daily
Each habit document contains:
interface Habit {
id: string;
name: string;
type: 'new' | 'quit';
streak: number; // Current consecutive days
target: number; // Goal days (e.g., 21 days)
completed: boolean; // Today's completion status
category: string; // Habit category
userId: string; // User identifier
createdAt: Date; // Creation timestamp
updatedAt: Date; // Last update timestamp
completedDates: string[]; // Array of completion dates
weeklyProgress: { // Weekly tracking object
monday: boolean;
tuesday: boolean;
wednesday: boolean;
thursday: boolean;
friday: boolean;
saturday: boolean;
sunday: boolean;
};
}// Calculate streak from completed dates
const sortedDates = completedDates.sort();
let currentStreak = 0;
let lastDate = new Date(today);
// Count consecutive days backwards from today
for (let i = sortedDates.length - 1; i >= 0; i--) {
const checkDate = new Date(sortedDates[i]);
const diffTime = Math.abs(lastDate.getTime() - checkDate.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays <= 1) { // Consecutive day found
currentStreak++;
lastDate = checkDate;
} else {
break; // Gap found, stop counting
}
}
streak = currentStreak;- Sort Dates: Arrange completion dates chronologically
- Backward Count: Start from today and count backwards
- Consecutive Check: Only count days with โค1 day gap
- Break on Gap: Stop counting when a gap is found
- Update Streak: Store the calculated streak value
- Unchecking Today: Removes the date from completedDates and recalculates streak
- Streak Reset: If you uncheck today, your streak drops to 0 (no consecutive days remain)
- No Grace Period: Currently no "streak protection" for missed days
- Immediate Impact: Streak changes instantly when habit status changes
- Habit Ticked: Recalculates and increases streak
- Habit Un-ticked: Recalculates and decreases streak
- Date Changes: Updates streak based on new completion date
- Real-time Sync: All changes immediately reflect in UI and database
The weekly progress shows total habits completed per day, not individual days:
// In stats API (src/app/api/habits/stats/route.ts)
habits.forEach((habit: any) => {
if (habit.weeklyProgress) {
Object.keys(habit.weeklyProgress).forEach((day: string) => {
if (habit.weeklyProgress[day]) {
weeklyProgress[day]++; // Counts habits, not days
}
});
}
});Example: If you have 6 habits and complete all 6 on Monday:
- Monday shows "6 days completed"
- This represents 6 habits ร 1 day = 6 completions
- Not 6 individual calendar days
// Find users with 3+ week streaks
habits.where('streak', '>=', 21)
// Find users with 100+ day streaks
habits.where('streak', '>=', 100)
// Find habits completed on specific dates
habits.where('completedDates', 'array-contains-any', ['2024-01-15', '2024-01-16'])
// Find longest streaks
habits.orderBy('streak', 'desc').limit(10)- Current Streak: Shows today's streak count
- Progress Bar: Visual representation of streak vs target
- Streak Icons: Flame emojis and visual indicators
- Daily Status: Clear indication of today's completion
- 7-Day View: Shows completion status for each day of the week
- Visual Indicators: Checkmarks and progress indicators
- Weekly Totals: Count of completed days this week
GET /api/habits- Fetch all user habits with current streaksPOST /api/habits- Create new habit (starts with streak = 0)PATCH /api/habits/[id]- Update habit completion and recalculate streakPUT /api/habits/[id]- Update habit detailsDELETE /api/habits/[id]- Delete habit
GET /api/habits/stats- Get comprehensive streak statistics- Total active streaks across all habits
- Success rates and completion metrics
- Weekly progress summaries
- Time-based analytics
- Current streak count
- Longest streak achieved
- Completion percentage (streak/target)
- Weekly completion patterns
- Total active streaks across all habits
- Overall success rate
- Weekly completion trends
- Time investment estimates
// Check streak calculation in browser console
console.log('Habit Data:', habit);
console.log('Completed Dates:', habit.completedDates);
console.log('Current Streak:', habit.streak);
console.log('Weekly Progress:', habit.weeklyProgress);// In Firestore console, verify:
// 1. completedDates array contains actual dates
// 2. streak field updates with each completion
// 3. weeklyProgress object reflects daily status
// 4. updatedAt timestamp changes with each update- Daily Ticking: Tick habits every day to maintain streaks
- Consistent Timing: Try to complete habits at similar times
- Missed Days: Don't worry - streaks can be rebuilt
- Goal Setting: Set realistic target days (21, 30, 100)
- Date Handling: Always use ISO date strings for consistency
- Streak Recalculation: Recalculate on every completion change
- Data Validation: Ensure completedDates are valid dates
- Performance: Streak calculation is lightweight and fast
- Streak Milestones: Badges for 7, 21, 100 day achievements
- Streak Recovery: Allow 1-2 missed days without breaking
- Longest Streak Tracking: Store historical best streaks
- Streak Analytics: Detailed streak pattern analysis
- Social Features: Share streaks with friends
- Streak Heatmaps: Visual streak patterns over time
- Predictive Analytics: Estimate habit success probability
- Streak Comparison: Compare streaks across different habits
- Seasonal Patterns: Identify seasonal habit trends
Current Issue: Unchecking a habit immediately resets streak to 0
// Current behavior in PATCH /api/habits/[id]/route.ts
if (!completed) {
// Remove today from completed dates
completedDates = completedDates.filter((d: string) => d !== today);
// Streak immediately recalculates to 0 if no consecutive days remain
}Proposed Solutions:
- Streak Protection: Allow 1-2 missed days without breaking streak
- Streak Recovery: Grace period to "catch up" on missed days
- Confirmation Dialog: Warn users before breaking a long streak
- Streak History: Show longest streak vs current streak
Current Issue: Weekly chart shows "habits ร days" not actual calendar days
// Current calculation counts habits, not days
weeklyProgress[day]++; // This counts habits completed on that dayProposed Solutions:
- Clarify Labels: Change "6 days completed" to "6 habits completed today"
- Dual Display: Show both habits completed and actual days tracked
- Visual Separation: Different indicators for habits vs calendar days
- Tooltip Explanations: Hover to see what the numbers represent
Critical Problem: New users see impossible completion dates
Current Behavior:
// Current weekly display always shows Monday-Sunday
const weekDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
// Problem: User signs up Thursday, but sees Monday-Wednesday as available
// This creates confusion about when habits can actually be completedReal-World Example:
- User signs up: Thursday, January 18th
- Current week display: Shows Monday (Jan 15) through Sunday (Jan 21)
- Impossible scenario: User sees Monday-Wednesday as "available" for completion
- User confusion: "How did I complete habits on Monday if I wasn't even signed up?"
Proposed Solution - Dynamic Week Starting:
// Calculate week start based on habit creation date
const getWeekStart = (habitCreatedAt: Date) => {
const createdDate = new Date(habitCreatedAt);
const dayOfWeek = createdDate.getDay(); // 0 = Sunday, 1 = Monday, etc.
// Start week from the day the habit was created
const weekStart = new Date(createdDate);
weekStart.setDate(createdDate.getDate() - dayOfWeek);
return weekStart;
};
// Generate week days from creation date forward
const generateWeekDays = (startDate: Date) => {
const days = [];
for (let i = 0; i < 7; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
days.push({
date: date.toISOString().split('T')[0],
dayName: date.toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase(),
isFuture: date > new Date()
});
}
return days;
};Enhanced Weekly Display:
// Show week starting from habit creation, not generic Monday-Sunday
const weekDisplay = generateWeekDays(getWeekStart(habit.createdAt));
// Only show days from creation forward
weekDisplay.forEach(day => {
if (day.isFuture) {
// Show as "not yet available" or "future day"
displayFutureDay(day);
} else {
// Show completion status for past/current days
displayCompletionStatus(day);
}
});Benefits of Dynamic Week Starting:
- โ Logical consistency: Week starts when user actually begins
- โ No impossible dates: Users can't see completion options for pre-signup days
- โ Better UX: Clear progression from habit creation forward
- โ Accurate tracking: Progress reflects actual user journey
1. Backend Changes (src/app/api/habits/stats/route.ts):
// Replace static week calculation with dynamic week starting
export async function GET(request: NextRequest) {
// ... existing auth code ...
// Calculate weekly progress based on habit creation dates
const weeklyProgress: Record<string, number> = {};
const weekStartDates: string[] = [];
habits.forEach((habit: any) => {
if (habit.createdAt) {
const createdDate = habit.createdAt.toDate ? habit.createdAt.toDate() : new Date(habit.createdAt);
const weekStart = getWeekStart(createdDate);
weekStartDates.push(weekStart.toISOString().split('T')[0]);
// Only count completions from creation date forward
if (habit.weeklyProgress) {
Object.keys(habit.weeklyProgress).forEach((day: string) => {
if (habit.weeklyProgress[day]) {
weeklyProgress[day] = (weeklyProgress[day] || 0) + 1;
}
});
}
}
});
// Calculate this week's completion based on actual available days
const earliestStart = weekStartDates.length > 0 ? Math.min(...weekStartDates.map(d => new Date(d))) : new Date();
const thisWeekCompleted = calculateAvailableCompletions(habits, earliestStart);
// ... rest of stats calculation ...
}
function getWeekStart(createdDate: Date): Date {
const dayOfWeek = createdDate.getDay();
const weekStart = new Date(createdDate);
weekStart.setDate(createdDate.getDate() - dayOfWeek);
return weekStart;
}
function calculateAvailableCompletions(habits: any[], weekStart: Date): number {
const today = new Date();
let totalCompletions = 0;
habits.forEach(habit => {
if (habit.createdAt) {
const createdDate = habit.createdAt.toDate ? habit.createdAt.toDate() : new Date(habit.createdAt);
// Only count completions from creation date forward
if (createdDate <= today) {
// Count completions for available days
const availableDays = Math.ceil((today.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24)) + 1;
totalCompletions += Math.min(availableDays, 7); // Cap at 7 days
}
}
});
return totalCompletions;
}2. Frontend Changes (src/app/Habits/page.tsx):
// Replace static week display with dynamic week
const WeeklyProgress = ({ habits }: { habits: Habit[] }) => {
const [weekDays, setWeekDays] = useState<any[]>([]);
useEffect(() => {
if (habits.length > 0) {
// Find earliest habit creation date
const creationDates = habits.map(h => h.createdAt).filter(Boolean);
if (creationDates.length > 0) {
const earliestDate = new Date(Math.min(...creationDates.map(d => new Date(d))));
const dynamicWeek = generateDynamicWeek(earliestDate);
setWeekDays(dynamicWeek);
}
}
}, [habits]);
const generateDynamicWeek = (startDate: Date) => {
const days = [];
for (let i = 0; i < 7; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
const isFuture = date > new Date();
const isPast = date < new Date();
days.push({
date: date.toISOString().split('T')[0],
dayName: date.toLocaleDateString('en-US', { weekday: 'short' }).toUpperCase(),
isFuture,
isPast,
isToday: date.toDateString() === new Date().toDateString()
});
}
return days;
};
return (
<div className="grid grid-cols-7 gap-2 mb-4">
{weekDays.map((day, index) => (
<div key={day.date} className="text-center">
<div className="text-xs text-neutral-400 mb-2">{day.dayName}</div>
<div className={`w-8 h-8 rounded-lg flex items-center justify-center text-xs font-medium ${
day.isFuture ? 'bg-neutral-700 text-neutral-500' : // Future days
day.isToday ? 'bg-blue-500/20 text-blue-400' : // Today
'bg-green-500/20 text-green-400' // Past days
}`}>
{day.isFuture ? 'โ' : day.isToday ? '!' : 'โ'}
</div>
</div>
))}
</div>
);
};3. Data Structure Updates:
// Enhanced habit interface with week tracking
interface Habit {
// ... existing fields ...
weekStartDate: string; // When this habit's week begins
availableDays: number; // How many days are available for completion
weekProgress: { // Progress for available days only
[date: string]: boolean; // Date-based instead of day-of-week
};
}- Streak Milestones: Celebrate 7, 21, 100 day achievements
- Streak Motivation: Show progress toward next milestone
- Missed Day Recovery: Allow users to "make up" missed days
- Streak Streaks: Track longest streak ever achieved
- Habit Streak Comparison: Compare streaks across different habits
// Proposed enhanced habit structure
interface EnhancedHabit {
// ... existing fields ...
longestStreak: number; // Best streak ever achieved
streakStartDate: string; // When current streak started
missedDays: number; // Days missed in current streak
gracePeriodUsed: boolean; // Whether grace period was used
streakHistory: { // Track streak patterns
startDate: string;
endDate: string;
duration: number;
}[];
}- Streak Calculation: O(n) complexity where n = completed dates
- Database Updates: Single document update per completion
- Real-time Updates: Immediate UI refresh after API calls
- Caching: Streak data cached in React state for performance
- Date Validation: Ensures all dates are valid ISO strings
- Streak Consistency: Streak always matches completedDates
- Atomic Updates: All related fields updated in single transaction
- User Isolation: Each user's streaks are completely separate
- User Growth: System scales with user count
- Habit Growth: Each user can have unlimited habits
- Date Storage: Efficient string-based date storage
- Query Optimization: Indexed queries for fast streak retrieval
src/
โโโ app/
โ โโโ middleware.ts # Route protection middleware
โ โโโ layout.tsx # Root layout with AuthProvider
โ โโโ Dashboard/ # Protected route
โ โโโ Habits/ # Protected route
โ โโโ Achievements/ # Protected route
โ โโโ GritEngine/ # Protected route
โ โโโ Inbox/ # Protected route
โ โโโ Pages/ # Protected route
โ โโโ api/
โ โโโ auth/
โ โโโ verify/route.ts # Token verification API
โ โโโ logout/route.ts # Logout API
โโโ components/
โ โโโ ProtectedRoute.tsx # Route protection component
โ โโโ Join.tsx # Login component
โ โโโ NavBar.tsx # Navigation with logout
โโโ context/
โ โโโ AuthContext.tsx # Authentication state management
โโโ lib/
โโโ firebase.ts # Firebase client configuration
โโโ firebaseAdmin.ts # Firebase admin configuration
โโโ cookies.ts # Cookie utility functions
- Location:
src/app/middleware.ts - Purpose: Server-side route protection
- Protected Routes:
/Dashboard/*/Pages/*/Achievements/*/Habits/*/GritEngine/*/Inbox/*/user/*
- Location:
src/components/ProtectedRoute.tsx - Purpose: Client-side authentication checks
- Features:
- Loading state management
- Automatic redirects
- User session validation
- Location:
src/context/AuthContext.tsx - Purpose: Global authentication state management
- Features:
- User session management
- Automatic token refresh
- Sign-out functionality
// src/app/middleware.ts
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value;
// Check if user has valid token
if (!token || token.length < 100) {
// Redirect to login for protected routes
if (isProtectedRoute(request.nextUrl.pathname)) {
return NextResponse.redirect(new URL('/', request.url));
}
}
return NextResponse.next();
}// src/components/ProtectedRoute.tsx
export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push('/');
}
}, [user, loading, router]);
if (loading) return <Loader />;
if (!user) return null;
return <>{children}</>;
}// Example: Protecting a page
import ProtectedRoute from '../components/ProtectedRoute';
const MyProtectedPage = () => {
return (
<ProtectedRoute>
<div>This content is only visible to authenticated users</div>
</ProtectedRoute>
);
};// Example: Using auth in components
import { useAuth } from '../context/AuthContext';
const MyComponent = () => {
const { user, signOut, loading } = useAuth();
if (loading) return <div>Loading...</div>;
return (
<div>
<p>Welcome, {user?.displayName}!</p>
<button onClick={signOut}>Sign Out</button>
</div>
);
};User clicks "Join Now" โ Firebase Google Sign-In โ ID Token generated โ Stored in secure cookie
User visits protected route โ Middleware checks token โ ProtectedRoute component validates โ Page content displayed
Token expires โ Middleware detects invalid token โ User redirected to login โ Re-authentication required
User clicks logout โ Firebase sign-out โ Cookie cleared โ Redirected to home page
- httpOnly:
true(prevents XSS attacks) - secure:
true(HTTPS only in production) - sameSite:
strict(prevents CSRF attacks) - maxAge:
3600seconds (1 hour expiration)
// Set authentication cookie
setCookie('token', idToken, { maxAge: 3600 });
// Get cookie value
const token = getCookie('token');
// Delete cookie
deleteCookie('token');
// Clear all cookies
clearAllCookies();-
No Authentication Token
- Redirect to login page
- Clear any invalid cookies
-
Token Expired
- Automatic redirect to login
- Clear expired token
-
Invalid Token
- Middleware rejection
- Component-level protection
-
Network Errors
- Graceful fallback
- User-friendly error messages
// In browser console
console.log('Cookies:', document.cookie);
console.log('Auth State:', useAuth());- Check browser network tab for redirects
- Verify cookie presence in Application tab
- Monitor console for authentication errors
-
Token not being set
- Check Firebase configuration
- Verify environment variables
-
Redirects not working
- Clear browser cache
- Check middleware configuration
-
Protected routes accessible
- Verify ProtectedRoute wrapper
- Check authentication context
POST /api/auth/verify- Verify Firebase tokensPOST /api/auth/logout- Handle user logout
GET /api/pages- Fetch user pagesPOST /api/pages- Create new pageDELETE /api/pages- Delete pagesGET /api/user- Get user data
- Token refresh automation
- Role-based access control
- Session management
- Multi-factor authentication
- Audit logging
- Token caching
- Lazy loading of protected routes
- Optimistic UI updates
- Use TypeScript for type safety
- Follow React best practices
- Implement proper error boundaries
- Write comprehensive tests
- Never expose sensitive data in client code
- Validate all user inputs
- Use HTTPS in production
- Regular security audits
For authentication-related issues:
- Check the debugging section above
- Verify environment variables
- Clear browser cache and cookies
- Restart development server
Last Updated: December 2024
Version: 1.0.0
Maintainer: Development Team
-
Weekly Progress Display Logic - โ IMPLEMENTED
- Dynamic week starting based on habit creation date
- No more impossible completion dates for new users
- Week display adapts to when habits were created
-
Streak System - โ WORKING CORRECTLY
- Real-time streak calculation (not hardcoded)
- Proper streak accumulation over weeks/months
- Daily tick availability for maintaining streaks
-
Frontend Weekly Display - โ IMPROVED
- Dynamic visual indicators for past, present, and future days
- Proper date display with month/day format
- Visual legend explaining different day states
- Better user experience with clear status indicators
-
Harsh Streak Reset Behavior - ๐ NEEDS IMPROVEMENT
- Unchecking a habit immediately resets streak to 0
- No grace period for missed days
- Users lose progress instantly
-
Weekly Progress Calculation - โ FIXED
Still shows "habits ร days" not actual calendar daysLabel says "X habits completed" but calculation is still based on habit count- Now correctly counts actual completions per day
-
Actual Completion Tracking - โ IMPLEMENTED
Weekly display shows placeholder completion statusNo real-time connection between habit completion and weekly progressTODO: Implement actual completion tracking per day- Real-time completion tracking per day is now working
- Each day shows actual completion count from habit data
-
Streak Protection System
- Allow 1-2 missed days without breaking streak
- Grace period to "catch up" on missed days
- Confirmation dialog before breaking long streaks
-
Weekly Progress Clarity
- Implement real completion tracking per day
- Connect habit completion status to weekly display
- Show actual vs. target completion for each day
-
Enhanced User Experience
- Add tooltips showing completion details for each day
- Implement weekly goal setting and tracking
- Add weekly streak milestones and achievements