Block 8 complete: streaks, notifications, referrals, premium identity, gamification

This commit is contained in:
root
2026-06-15 10:32:10 +02:00
parent 6423430275
commit 5e73d1d7ad
24 changed files with 1565 additions and 38 deletions
+63 -1
View File
@@ -18,6 +18,22 @@ interface User {
trialEndsAt?: string;
}
interface StreakData {
currentStreak: number;
longestStreak: number;
lastClaimDate: string | null;
canClaim: boolean;
streakFreeze: number;
todayReward: number;
}
interface XpData {
level: number;
xp: number;
xpToNext: number;
totalXp: number;
}
interface AuthContextType {
user: User | null;
token: string | null;
@@ -26,6 +42,10 @@ interface AuthContextType {
register: (email: string, name: string, password: string) => Promise<void>;
logout: () => void;
refreshUser: () => Promise<void>;
streak: StreakData | null;
xp: XpData | null;
refreshStreak: () => Promise<void>;
refreshXp: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -34,6 +54,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [streak, setStreak] = useState<StreakData | null>(null);
const [xp, setXp] = useState<XpData | null>(null);
useEffect(() => {
const stored = localStorage.getItem("flh_token");
@@ -100,6 +122,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
localStorage.removeItem("flh_token");
setToken(null);
setUser(null);
setStreak(null);
setXp(null);
}, []);
const refreshUser = useCallback(async () => {
@@ -107,8 +131,46 @@ export function AuthProvider({ children }: { children: ReactNode }) {
await fetchUser(token);
}, [token]);
const refreshStreak = useCallback(async () => {
if (!token) return;
try {
const res = await fetch("/api/gamification/streak", {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
const data = await res.json();
setStreak(data);
}
} catch {
// ignore
}
}, [token]);
const refreshXp = useCallback(async () => {
if (!token) return;
try {
const res = await fetch("/api/gamification/xp", {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
const data = await res.json();
setXp(data);
}
} catch {
// ignore
}
}, [token]);
// Fetch streak and XP when token becomes available
useEffect(() => {
if (token && user) {
refreshStreak();
refreshXp();
}
}, [token, user, refreshStreak, refreshXp]);
return (
<AuthContext.Provider value={{ user, token, loading, login, register, logout, refreshUser }}>
<AuthContext.Provider value={{ user, token, loading, login, register, logout, refreshUser, streak, xp, refreshStreak, refreshXp }}>
{children}
</AuthContext.Provider>
);