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
564 changes: 282 additions & 282 deletions .idea/libraries/Dart_Packages.xml

Large diffs are not rendered by default.

46 changes: 23 additions & 23 deletions .idea/libraries/Dart_SDK.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 22 additions & 22 deletions .idea/libraries/Flutter_Plugins.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions backend/src/routes/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@ router.get('/stats', async (req: AuthRequest, res: Response, next: NextFunction)
}
});

// POST /user/stats - Update gamification stats
router.post('/stats', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const userId = req.user!.userId;
// Expecting body to have keys matching our snake_case logic from frontend?
// Wait, the frontend sends snake_case keys now: current_level, current_xp, etc.
// But the service expects UserStatsResult which has camelCase keys!
// I need to map the request body to the service interface.

const { current_level, current_xp, total_gold, avatar_stage } = req.body;

const updatedStats = await statsService.updateUserStats(userId, {
currentLevel: current_level,
currentXP: current_xp,
totalGold: total_gold,
avatarStage: avatar_stage
});

res.json({ stats: updatedStats });
} catch (e) {
next(e);
}
});

// GET /user/progress - Study progress
router.get('/progress', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
Expand Down
21 changes: 21 additions & 0 deletions backend/src/services/stats.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ export async function getUserStats(userId: string): Promise<UserStatsResult> {
};
}

export async function updateUserStats(userId: string, stats: UserStatsResult): Promise<UserStatsResult> {
const res = await pool.query(
`INSERT INTO user_stats (user_id, current_level, current_xp, total_gold, avatar_stage)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (user_id) DO UPDATE SET
current_level = EXCLUDED.current_level,
current_xp = EXCLUDED.current_xp,
total_gold = EXCLUDED.total_gold,
avatar_stage = EXCLUDED.avatar_stage
RETURNING current_level, current_xp, total_gold, avatar_stage`,
[userId, stats.currentLevel, stats.currentXP, stats.totalGold, stats.avatarStage]
);

return {
currentLevel: res.rows[0].current_level,
currentXP: res.rows[0].current_xp,
totalGold: res.rows[0].total_gold,
avatarStage: res.rows[0].avatar_stage
};
}

export async function getUserProgress(userId: string): Promise<UserProgressResult> {
// Calculate aggregate study stats
// We sum duration_seconds / 60 for minutes
Expand Down
22 changes: 16 additions & 6 deletions lib/features/auth/application/auth_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,26 @@ class AuthController extends _$AuthController {
// Check for existing session on app init
_checkSession();

// Initially show unauthenticated state
// Will update if session exists
return const AuthStateUnauthenticated();
// Initially show loading state to prevent flash of login screen
return const AuthStateLoading();
}

/// Check if user session exists (called on app init)
Future<void> _checkSession() async {
final user = await _repository.getCurrentUser();
if (user != null && ref.mounted) {
state = AuthStateAuthenticated(user);
try {
final user = await _repository.getCurrentUser();

if (!ref.mounted) return;

if (user != null) {
state = AuthStateAuthenticated(user);
} else {
state = const AuthStateUnauthenticated();
}
} catch (e) {
if (ref.mounted) {
state = const AuthStateUnauthenticated();
}
}
}

Expand Down
17 changes: 10 additions & 7 deletions lib/features/goals/presentation/goal_detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,25 @@ class _GoalDetailScreenState extends ConsumerState<GoalDetailScreen> {
.read(apiGoalRepositoryProvider)
.updateTask(widget.goal.id, updatedTask);

// Award gold for newly completed tasks
// Award XP for newly completed tasks
if (!wasCompleted && updatedTask.isCompleted) {
final currentStats = ref.read(userStatsProvider);
final newStats = GamificationService.awardTaskRewards(currentStats);
ref.read(userStatsProvider.notifier).updateStats(newStats);
// Award rewards logic
ref.read(userStatsProvider.notifier).awardTaskRewards();

// Get XP amount for popup display
final currentStage = ref.read(userStatsProvider).stage;
final xpEarned = GamificationService.calculateTaskXpReward(
currentStage,
);

// Show reward popup
if (mounted) {
Future.delayed(const Duration(milliseconds: 300), () {
showDialog<void>(
context: context,
builder: (context) => TaskCompletionPopup(
goldEarned: GamificationService.calculateTaskGoldReward(
currentStats.stage,
),
taskName: task.title,
xpEarned: xpEarned,
onClose: () => Navigator.of(context).pop(),
),
);
Expand Down
Loading
Loading