Add gamification, micro-animations, and a mini-game for empty states

Applies a few Hook Model (trigger/action/reward/investment) patterns on top
of the existing swipe/match loop:

- Daily streak: a flame badge in the home header, backed by the new
  checkin endpoint — an external trigger to return daily.
- Profile completeness meter + badge collection on the profile page
  (client-computed from existing data) — the investment loop nudging
  users to fill out bio/photos/answers.
- Bio editor on the profile page, shown on swipe cards and candidate
  detail so profiles read richer.
- Catch the Hearts: a small tap mini-game (falling hearts, bonus items,
  20s timer, persisted high score) shown instead of a dead end when the
  discover feed runs out, so there's still a reason to stay in the app.
- Micro-animations: heart-burst on like, confetti on match, tap feedback
  on all buttons (active:scale-95).

Fixed a React "purity" lint violation along the way (Math.random() can't
be called inside useMemo under this project's rules — moved to useState
lazy initializers, which run once on mount as intended).
This commit is contained in:
2026-09-20 12:26:41 +03:30
parent 4bd7b93b4a
commit cbbfb145dc
11 changed files with 524 additions and 21 deletions
+20
View File
@@ -46,6 +46,8 @@ export interface UserProfile {
approval_status: ApprovalStatus;
rejection_reason?: string;
is_admin: boolean;
bio?: string;
streak_count: number;
}
export interface TokenPair {
@@ -125,6 +127,7 @@ export interface DiscoverCandidate {
id: number;
first_name: string;
age: number;
bio?: string;
photos: string[];
distance_km?: number;
interests?: string[];
@@ -139,6 +142,7 @@ export interface CandidateDetail {
id: number;
first_name: string;
age: number;
bio?: string;
photos: string[];
distance_km?: number;
height_cm?: number;
@@ -420,6 +424,22 @@ export function updateLocation(latitude: number, longitude: number) {
});
}
export function updateBio(bio: string) {
return authRequest<null>("/profile/bio", {
method: "PATCH",
body: JSON.stringify({ bio }),
});
}
export interface CheckInResult {
streak_count: number;
is_new_today: boolean;
}
export function checkIn() {
return authRequest<CheckInResult>("/profile/checkin", { method: "POST" });
}
// ---------------- دوستیابی ----------------
export function getDiscoverFeed(params?: {