feat(auth): include subscription tier in token and session payload

- Adds subscription tier info to access/refresh tokens.
- Stores tier details in session Redis record.
- Returns tier data in login/register response.
- Uses default tier when user has none.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-07 21:03:19 -03:00
1 parent 7f42def478
commit d27526e026
20 files changed
+2133 -40

No files matched your search

+60 -2
View File
@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useRef } from "react";
import { Bell } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -33,10 +33,32 @@ export function NotificationBell({ userId, className }: NotificationBellProps) {
const [notifications, setNotifications] = useState<Notification[]>([]);
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const rawUnreadRef = useRef(0);
const baselineUnreadRef = useRef(0);
const baselineLoadedRef = useRef(false);
const storageKeyRef = useRef<string | null>(null);
const effectiveUserId = userId || user?.userId;
// Buscar contagem de não lidas
const persistBaseline = (value: number) => {
baselineUnreadRef.current = value;
if (typeof window !== "undefined" && storageKeyRef.current) {
window.localStorage.setItem(storageKeyRef.current, String(value));
}
};
const loadBaselineFromStorage = () => {
if (typeof window === "undefined" || !storageKeyRef.current) {
return undefined;
}
const stored = window.localStorage.getItem(storageKeyRef.current);
if (!stored) return undefined;
const parsed = Number.parseInt(stored, 10);
if (Number.isNaN(parsed)) return undefined;
return parsed;
};
const fetchUnreadCount = async () => {
if (!effectiveUserId) return;
@@ -45,7 +67,20 @@ export function NotificationBell({ userId, className }: NotificationBellProps) {
if (res.ok) {
const data = await res.json();
if (data.success) {
setUnreadCount(data.count);
rawUnreadRef.current = data.count;
if (!baselineLoadedRef.current) {
const storedBaseline = loadBaselineFromStorage();
if (storedBaseline !== undefined) {
const normalized = Math.min(storedBaseline, data.count);
persistBaseline(normalized);
} else {
persistBaseline(data.count);
}
baselineLoadedRef.current = true;
} else if (data.count < baselineUnreadRef.current) {
persistBaseline(data.count);
}
setUnreadCount(Math.max(0, data.count - baselineUnreadRef.current));
}
}
} catch (error) {
@@ -74,6 +109,21 @@ export function NotificationBell({ userId, className }: NotificationBellProps) {
};
// Polling automático a cada 30 segundos
useEffect(() => {
if (!effectiveUserId) {
return;
}
storageKeyRef.current = `notifications:lastBaseline:${effectiveUserId}`;
const storedBaseline = loadBaselineFromStorage();
if (storedBaseline !== undefined) {
baselineUnreadRef.current = storedBaseline;
baselineLoadedRef.current = true;
} else {
baselineUnreadRef.current = 0;
baselineLoadedRef.current = false;
}
}, [effectiveUserId]);
useEffect(() => {
if (!effectiveUserId) return;
@@ -94,6 +144,8 @@ export function NotificationBell({ userId, className }: NotificationBellProps) {
useEffect(() => {
if (isOpen) {
fetchNotifications();
persistBaseline(rawUnreadRef.current);
setUnreadCount(0);
}
}, [isOpen]);
@@ -111,6 +163,12 @@ export function NotificationBell({ userId, className }: NotificationBellProps) {
)
);
// Atualizar contagem
rawUnreadRef.current = Math.max(0, rawUnreadRef.current - 1);
const newBaseline = Math.min(
baselineUnreadRef.current,
rawUnreadRef.current
);
persistBaseline(newBaseline);
setUnreadCount((prev) => Math.max(0, prev - 1));
}
} catch (error) {