frontend changes

This commit is contained in:
Chandini 2025-09-17 17:13:22 +05:30
parent 906f763a78
commit 4983f7a2e6
10 changed files with 63 additions and 75 deletions

View File

@ -129,13 +129,19 @@ const addTokenRefreshInterceptor = (client: typeof authApiClient) => {
// No refresh token available // No refresh token available
clearTokens(); clearTokens();
safeLocalStorage.removeItem('codenuk_user'); safeLocalStorage.removeItem('codenuk_user');
window.location.href = '/signin'; // Prevent redirect loops by checking current location
if (typeof window !== 'undefined' && !window.location.pathname.includes('/signin')) {
window.location.href = '/signin?error=Please sign in to continue.';
}
return Promise.reject(error); return Promise.reject(error);
} catch (refreshError) { } catch (refreshError) {
console.error('Token refresh failed:', refreshError); console.error('Token refresh failed:', refreshError);
clearTokens(); clearTokens();
safeLocalStorage.removeItem('codenuk_user'); safeLocalStorage.removeItem('codenuk_user');
window.location.href = '/signin'; // Prevent redirect loops by checking current location
if (typeof window !== 'undefined' && !window.location.pathname.includes('/signin')) {
window.location.href = '/signin?error=Session expired. Please sign in again.';
}
return Promise.reject(refreshError); return Promise.reject(refreshError);
} }
} }

View File

@ -48,12 +48,18 @@ export function SignInForm({ }: SignInFormProps) {
// Persist for refresh // Persist for refresh
localStorage.setItem("codenuk_user", JSON.stringify(response.data.user)) localStorage.setItem("codenuk_user", JSON.stringify(response.data.user))
// Redirect based on user role // Redirect based on user role with error handling
try {
if (response.data.user.role === 'admin') { if (response.data.user.role === 'admin') {
router.push("/admin") router.push("/admin")
} else { } else {
router.push("/") router.push("/")
} }
} catch (redirectError) {
console.error('Redirect failed:', redirectError)
// Fallback redirect
window.location.href = response.data.user.role === 'admin' ? '/admin' : '/'
}
} else { } else {
setError("Invalid response from server. Please try again.") setError("Invalid response from server. Please try again.")
} }

View File

@ -72,7 +72,14 @@ export function SignUpForm({ onSignUpSuccess }: SignUpFormProps) {
} else { } else {
// Default behavior - redirect to signin with message // Default behavior - redirect to signin with message
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
router.push("/signin?message=Account created successfully! Please check your email to verify your account.") const message = encodeURIComponent("Account created successfully! Please check your email to verify your account.")
try {
router.push(`/signin?message=${message}`)
} catch (redirectError) {
console.error('Signup form redirect failed:', redirectError)
// Fallback redirect
window.location.href = `/signin?message=${message}`
}
} }
} }
} else { } else {

View File

@ -12,12 +12,19 @@ export function SignUpPage() {
const handleSignUpSuccess = () => { const handleSignUpSuccess = () => {
setIsSuccess(true) setIsSuccess(true)
// Redirect to signin after 3 seconds // Redirect to signin after 3 seconds with proper URL encoding
setTimeout(() => { setTimeout(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
router.push("/signin?message=Please check your email to verify your account") const message = encodeURIComponent("Please check your email to verify your account")
try {
router.push(`/signin?message=${message}`)
} catch (redirectError) {
console.error('Signup redirect failed:', redirectError)
// Fallback redirect
window.location.href = `/signin?message=${message}`
} }
}, 3001) }
}, 3000)
} }
if (isSuccess) { if (isSuccess) {

View File

@ -1,28 +1,14 @@
/**
* Centralized Backend Configuration
* Single source of truth for all backend URLs
*/
// Main backend URL - change this to update all API calls //
// LIVE PRODUCTION URL (Currently Active) // export const BACKEND_URL = 'https://backend.codenuk.com';
export const BACKEND_URL = 'https://backend.codenuk.com';
// LOCAL DEVELOPMENT URL (Uncomment this and comment above for local development) export const BACKEND_URL = 'http://localhost:8000';
// export const BACKEND_URL = 'http://localhost:8000';
// Realtime notifications socket URL (Template Manager emits notifications)
// Uses the same URL as BACKEND_URL for consistency
export const SOCKET_URL = BACKEND_URL; export const SOCKET_URL = BACKEND_URL;
// Export for backward compatibility
export const API_BASE_URL = BACKEND_URL; export const API_BASE_URL = BACKEND_URL;
// Helper function to get full API endpoint URL
export const getApiUrl = (endpoint: string): string => { export const getApiUrl = (endpoint: string): string => {
// Remove leading slash if present to avoid double slashes
const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint; const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
return `${BACKEND_URL}/${cleanEndpoint}`; return `${BACKEND_URL}/${cleanEndpoint}`;
}; };

View File

@ -30,7 +30,6 @@ export function AdminNotificationProvider({ children }: { children: React.ReactN
onAllNotificationsRead onAllNotificationsRead
} = useNotificationSocket(); } = useNotificationSocket();
// Load initial notifications
const refreshNotifications = async () => { const refreshNotifications = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
@ -43,7 +42,6 @@ export function AdminNotificationProvider({ children }: { children: React.ReactN
} }
}; };
// Mark single notification as read
const markAsRead = async (id: string) => { const markAsRead = async (id: string) => {
try { try {
await adminApi.markNotificationAsRead(id); await adminApi.markNotificationAsRead(id);
@ -56,7 +54,6 @@ export function AdminNotificationProvider({ children }: { children: React.ReactN
} }
}; };
// Mark all notifications as read
const markAllAsRead = async () => { const markAllAsRead = async () => {
try { try {
await adminApi.markAllNotificationsAsRead(); await adminApi.markAllNotificationsAsRead();
@ -69,18 +66,16 @@ export function AdminNotificationProvider({ children }: { children: React.ReactN
} }
}; };
// Clear all notifications from UI (after marking as read server-side)
const clearAll = async () => { const clearAll = async () => {
await markAllAsRead(); await markAllAsRead();
setNotifications([]); setNotifications([]);
}; };
// Remove notifications tied to a specific backend reference (e.g., feature/template id)
const removeByReference = (referenceType: string, referenceId: string) => { const removeByReference = (referenceType: string, referenceId: string) => {
setNotifications(prev => prev.filter(n => !(n.reference_type === referenceType && n.reference_id === referenceId))); setNotifications(prev => prev.filter(n => !(n.reference_type === referenceType && n.reference_id === referenceId)));
}; };
// Set up WebSocket event handlers
useEffect(() => { useEffect(() => {
onNewNotification((notification: AdminNotification) => { onNewNotification((notification: AdminNotification) => {
setNotifications(prev => [notification, ...prev]); setNotifications(prev => [notification, ...prev]);
@ -99,13 +94,11 @@ export function AdminNotificationProvider({ children }: { children: React.ReactN
}); });
}, [onNewNotification, onNotificationRead, onAllNotificationsRead]); }, [onNewNotification, onNotificationRead, onAllNotificationsRead]);
// Load notifications on mount
useEffect(() => { useEffect(() => {
refreshNotifications(); refreshNotifications();
}, []); }, []);
// When socket connects (e.g., navigating to admin), refresh to include any
// notifications created before the connection was established
useEffect(() => { useEffect(() => {
if (isConnected) { if (isConnected) {
refreshNotifications(); refreshNotifications();

View File

@ -40,7 +40,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}) })
// Only restore user if we have both user data AND at least one token
if (stored && (accessToken || refreshToken)) { if (stored && (accessToken || refreshToken)) {
try { try {
const userData = JSON.parse(stored) const userData = JSON.parse(stored)
@ -53,7 +52,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} }
} else { } else {
console.log('🔐 [AuthContext] No valid user session found') console.log('🔐 [AuthContext] No valid user session found')
// Clear any partial data
if (stored && !accessToken && !refreshToken) { if (stored && !accessToken && !refreshToken) {
console.log('🔐 [AuthContext] Clearing orphaned user data without tokens') console.log('🔐 [AuthContext] Clearing orphaned user data without tokens')
safeLocalStorage.removeItem('codenuk_user') safeLocalStorage.removeItem('codenuk_user')
@ -62,7 +60,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setIsLoading(false) setIsLoading(false)
}, []) }, [])
// Listen for storage changes to sync auth state across tabs
useEffect(() => { useEffect(() => {
const handleStorageChange = (e: StorageEvent) => { const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'codenuk_user' || e.key === 'accessToken' || e.key === 'refreshToken') { if (e.key === 'codenuk_user' || e.key === 'accessToken' || e.key === 'refreshToken') {
@ -99,20 +96,19 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const logout = async () => { const logout = async () => {
try { try {
// Call the logout API to invalidate tokens on the server
await logoutApi() await logoutApi()
} catch (error) { } catch (error) {
console.error('Logout API call failed:', error) console.error('Logout API call failed:', error)
// Continue with local logout even if API call fails
} finally { } finally {
// Always clear local data
setUser(null) setUser(null)
safeLocalStorage.removeItem('codenuk_user') safeLocalStorage.removeItem('codenuk_user')
safeLocalStorage.removeItem('accessToken') safeLocalStorage.removeItem('accessToken')
safeLocalStorage.removeItem('refreshToken') safeLocalStorage.removeItem('refreshToken')
// Redirect to signin page // Prevent redirect loops and add error context
window.location.href = '/signin' if (typeof window !== 'undefined' && !window.location.pathname.includes('/signin')) {
window.location.href = '/signin?error=You have been logged out'
}
} }
} }

View File

@ -33,8 +33,7 @@ export function useNotificationSocket(): UseNotificationSocketReturn {
const allNotificationsReadCallbackRef = useRef<(() => void) | null>(null); const allNotificationsReadCallbackRef = useRef<(() => void) | null>(null);
useEffect(() => { useEffect(() => {
// Initialize socket connection const templateManagerUrl = SOCKET_URL;
const templateManagerUrl = SOCKET_URL; // connect directly to template-manager socket server
const token = getAccessToken(); const token = getAccessToken();
console.log('[useNotificationSocket] Initializing socket', { console.log('[useNotificationSocket] Initializing socket', {
url: templateManagerUrl, url: templateManagerUrl,

View File

@ -6,7 +6,6 @@ import { useAuth } from '@/contexts/auth-context';
export function useTemplates() { export function useTemplates() {
const { user, isLoading: authLoading } = useAuth(); const { user, isLoading: authLoading } = useAuth();
// Log user ID for debugging
useEffect(() => { useEffect(() => {
console.log('🔍 [useTemplates] Current user ID:', user?.id || 'No user ID') console.log('🔍 [useTemplates] Current user ID:', user?.id || 'No user ID')
console.log('👤 [useTemplates] User object:', user) console.log('👤 [useTemplates] User object:', user)
@ -17,7 +16,6 @@ export function useTemplates() {
console.log('🔐 [useTemplates] Auth loading:', authLoading) console.log('🔐 [useTemplates] Auth loading:', authLoading)
console.log('🔐 [useTemplates] Timestamp:', new Date().toISOString()) console.log('🔐 [useTemplates] Timestamp:', new Date().toISOString())
// Check if we have tokens available
const accessToken = localStorage.getItem('accessToken') const accessToken = localStorage.getItem('accessToken')
const refreshToken = localStorage.getItem('refreshToken') const refreshToken = localStorage.getItem('refreshToken')
console.log('🔐 [useTemplates] Token availability:', { console.log('🔐 [useTemplates] Token availability:', {
@ -36,12 +34,11 @@ export function useTemplates() {
pagination?: { total?: number; limit?: number; offset?: number; hasMore?: boolean }; pagination?: { total?: number; limit?: number; offset?: number; hasMore?: boolean };
} | null>(null); } | null>(null);
// Stable categories fetched once, independent of paginated data
const [categories, setCategories] = useState<Array<{ id: string; name: string; count: number }>>([]); const [categories, setCategories] = useState<Array<{ id: string; name: string; count: number }>>([]);
const [paginationState, setPaginationState] = useState({ const [paginationState, setPaginationState] = useState({
currentPage: 0, currentPage: 0,
pageSize: 6, // Respect backend pagination: 6 items per page pageSize: 6,
total: 0, total: 0,
hasMore: false, hasMore: false,
loading: false, loading: false,
@ -51,7 +48,6 @@ export function useTemplates() {
const { show } = useToast(); const { show } = useToast();
// Fetch templates with pagination, category, and search
const fetchTemplatesWithPagination = useCallback(async (opts?: { const fetchTemplatesWithPagination = useCallback(async (opts?: {
page?: number; page?: number;
pageSize?: number; pageSize?: number;
@ -59,13 +55,11 @@ export function useTemplates() {
search?: string; search?: string;
resetPagination?: boolean; resetPagination?: boolean;
}) => { }) => {
// Don't fetch if auth is still loading
if (authLoading) { if (authLoading) {
console.log('[useTemplates] Auth still loading, skipping fetch') console.log('[useTemplates] Auth still loading, skipping fetch')
return; return;
} }
// Check if we have authentication tokens available
const accessToken = localStorage.getItem('accessToken') const accessToken = localStorage.getItem('accessToken')
const refreshToken = localStorage.getItem('refreshToken') const refreshToken = localStorage.getItem('refreshToken')
const hasTokens = !!(accessToken || refreshToken) const hasTokens = !!(accessToken || refreshToken)
@ -78,13 +72,10 @@ export function useTemplates() {
userId: user?.id userId: user?.id
}) })
// If user exists but no tokens, wait a bit for tokens to be loaded
if (user && !hasTokens) { if (user && !hasTokens) {
console.log('[useTemplates] User exists but no tokens found, waiting for token loading...') console.log('[useTemplates] User exists but no tokens found, waiting for token loading...')
// Wait a short time for tokens to be loaded from localStorage
await new Promise(resolve => setTimeout(resolve, 100)) await new Promise(resolve => setTimeout(resolve, 100))
// Check again after waiting
const retryAccessToken = localStorage.getItem('accessToken') const retryAccessToken = localStorage.getItem('accessToken')
const retryRefreshToken = localStorage.getItem('refreshToken') const retryRefreshToken = localStorage.getItem('refreshToken')
const retryHasTokens = !!(retryAccessToken || retryRefreshToken) const retryHasTokens = !!(retryAccessToken || retryRefreshToken)
@ -105,7 +96,7 @@ export function useTemplates() {
const { page = 0, pageSize = paginationState.pageSize, category = paginationState.selectedCategory, search = paginationState.searchQuery, resetPagination = false } = opts || {}; const { page = 0, pageSize = paginationState.pageSize, category = paginationState.selectedCategory, search = paginationState.searchQuery, resetPagination = false } = opts || {};
const offset = resetPagination ? 0 : page * pageSize; const offset = resetPagination ? 0 : page * pageSize;
const limit = pageSize; // Use pageSize for limit const limit = pageSize;
setPaginationState((prev) => ({ ...prev, loading: true })); setPaginationState((prev) => ({ ...prev, loading: true }));
setError(null); setError(null);
@ -116,7 +107,6 @@ export function useTemplates() {
console.log('[useTemplates] User object:', user); console.log('[useTemplates] User object:', user);
console.log('[useTemplates] includeOthers will be:', !user?.id); console.log('[useTemplates] includeOthers will be:', !user?.id);
// Only pass userId if it is a valid UUID v4; otherwise, omit and include others
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const validUserId = typeof user?.id === 'string' && uuidV4Regex.test(user.id) ? user.id : undefined; const validUserId = typeof user?.id === 'string' && uuidV4Regex.test(user.id) ? user.id : undefined;
if (!validUserId && user?.id) { if (!validUserId && user?.id) {
@ -125,7 +115,7 @@ export function useTemplates() {
const res = await templateService.getCombinedTemplates({ const res = await templateService.getCombinedTemplates({
userId: validUserId, userId: validUserId,
includeOthers: true, // always include custom templates from others + defaults includeOthers: true,
limit: pageSize, limit: pageSize,
offset, offset,
category: category === 'all' ? undefined : category, category: category === 'all' ? undefined : category,

View File

@ -78,11 +78,9 @@ export const AI_MOCKUP_CONFIG = {
// Export the main API base URL for backward compatibility // Export the main API base URL for backward compatibility
export const API_BASE_URL = API_CONFIG.BASE_URL; export const API_BASE_URL = API_CONFIG.BASE_URL;
// Temporary debug logging // Debug logging - shows centralized config is working
console.log('🔧 API Config Debug:', { console.log('🔧 API Config Debug:', {
'process.env.NEXT_PUBLIC_API_URL': process.env.NEXT_PUBLIC_API_URL, 'BACKEND_URL (centralized)': BACKEND_URL,
'process.env.NEXT_PUBLIC_AUTH_API_URL': process.env.NEXT_PUBLIC_AUTH_API_URL,
'DEFAULT_API_BASE_URL': DEFAULT_API_BASE_URL,
'API_CONFIG.BASE_URL': API_CONFIG.BASE_URL, 'API_CONFIG.BASE_URL': API_CONFIG.BASE_URL,
'API_CONFIG.AUTH_SERVICE': API_CONFIG.AUTH_SERVICE, 'API_CONFIG.AUTH_SERVICE': API_CONFIG.AUTH_SERVICE,
'API_BASE_URL': API_BASE_URL 'API_BASE_URL': API_BASE_URL