frontend changes
This commit is contained in:
parent
906f763a78
commit
4983f7a2e6
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,11 +48,17 @@ 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
|
||||||
if (response.data.user.role === 'admin') {
|
try {
|
||||||
router.push("/admin")
|
if (response.data.user.role === 'admin') {
|
||||||
} else {
|
router.push("/admin")
|
||||||
router.push("/")
|
} else {
|
||||||
|
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.")
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user