@djangocfg/api 2.1.102 → 2.1.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/api",
3
- "version": "2.1.102",
3
+ "version": "2.1.104",
4
4
  "description": "Auto-generated TypeScript API client with React hooks, SWR integration, and Zod validation for Django REST Framework backends",
5
5
  "keywords": [
6
6
  "django",
@@ -74,7 +74,6 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/ui-nextjs": "^2.1.102",
78
77
  "consola": "^3.4.2",
79
78
  "next": ">=16.0.0",
80
79
  "p-retry": "^7.0.0",
@@ -85,7 +84,7 @@
85
84
  "devDependencies": {
86
85
  "@types/node": "^24.7.2",
87
86
  "@types/react": "^19.0.0",
88
- "@djangocfg/typescript-config": "^2.1.102",
87
+ "@djangocfg/typescript-config": "^2.1.104",
89
88
  "next": "^16.0.0",
90
89
  "react": "^19.0.0",
91
90
  "tsup": "^8.5.0",
@@ -6,7 +6,7 @@ import React, {
6
6
  createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState
7
7
  } from 'react';
8
8
 
9
- import { useCfgRouter, useLocalStorage, useQueryParams } from '@djangocfg/ui-nextjs/hooks';
9
+ import { useCfgRouter, useLocalStorage, useQueryParams } from '../hooks';
10
10
 
11
11
  import { api as apiAccounts, Enums } from '../../';
12
12
  import { clearProfileCache, getCachedProfile } from '../hooks/useProfileCache';
@@ -1,5 +1,9 @@
1
1
  'use client';
2
2
 
3
+ // Router hooks
4
+ export { useCfgRouter } from './useCfgRouter';
5
+ export { useQueryParams } from './useQueryParams';
6
+
3
7
  // Core form hooks (decomposed)
4
8
  export { useAuthFormState, type UseAuthFormStateReturn } from './useAuthFormState';
5
9
  export { useAuthValidation, detectChannelFromIdentifier, validateIdentifier } from './useAuthValidation';
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
4
 
5
- import { useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
5
+ import { useCfgRouter } from './useCfgRouter';
6
6
 
7
7
  import { useAuth } from '../context';
8
8
 
@@ -3,7 +3,8 @@
3
3
  import { usePathname } from 'next/navigation';
4
4
  import { useEffect } from 'react';
5
5
 
6
- import { useCfgRouter, useQueryParams } from '@djangocfg/ui-nextjs/hooks';
6
+ import { useCfgRouter } from './useCfgRouter';
7
+ import { useQueryParams } from './useQueryParams';
7
8
 
8
9
  import { authLogger } from '../utils/logger';
9
10
 
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Universal Router Hook with BasePath Support
3
+ *
4
+ * Wrapper around Next.js useRouter that automatically handles basePath
5
+ * for static builds served via iframe or subdirectory
6
+ *
7
+ * IMPORTANT: In Next.js 15 App Router, router.push() does NOT automatically
8
+ * handle basePath (unlike Pages Router). This is a breaking change in App Router.
9
+ *
10
+ * This hook ensures basePath is always included when navigating, especially
11
+ * important for static exports served via iframe where basePath is critical.
12
+ *
13
+ * @see https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration
14
+ */
15
+
16
+ 'use client';
17
+
18
+ import { useRouter as useNextRouter } from 'next/navigation';
19
+ import { useCallback, useMemo } from 'react';
20
+
21
+ /**
22
+ * Get base path from environment variable
23
+ */
24
+ function getBasePath(): string {
25
+ if (typeof process === 'undefined') {
26
+ return '';
27
+ }
28
+ return process.env.NEXT_PUBLIC_BASE_PATH || '';
29
+ }
30
+
31
+ /**
32
+ * Add base path to a route path
33
+ */
34
+ function withBasePath(path: string, basePath: string): string {
35
+ if (!basePath) {
36
+ return path;
37
+ }
38
+ // Ensure path starts with /
39
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
40
+ // Remove trailing slash from basePath
41
+ const normalizedBasePath = basePath.replace(/\/$/, '');
42
+ return `${normalizedBasePath}${normalizedPath}`;
43
+ }
44
+
45
+ /**
46
+ * Router with basePath support
47
+ *
48
+ * Automatically adds basePath to all navigation methods when basePath is configured.
49
+ * In Next.js 15 App Router, router.push() doesn't handle basePath automatically,
50
+ * so this hook uses window.location to ensure basePath is always included.
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * const router = useCfgRouter();
55
+ *
56
+ * // With basePath='/cfg/admin':
57
+ * router.push('/dashboard'); // Client-side navigation to '/cfg/admin/dashboard'
58
+ * router.replace('/auth'); // Client-side replace with '/cfg/admin/auth'
59
+ * router.hardPush('/dashboard'); // Full page reload to '/cfg/admin/dashboard'
60
+ * router.hardReplace('/auth'); // Full page replace with '/cfg/admin/auth'
61
+ * ```
62
+ */
63
+ export function useCfgRouter() {
64
+ const router = useNextRouter();
65
+
66
+ // Get basePath and check if we're in static build mode
67
+ const basePath = useMemo(() => getBasePath(), []);
68
+ const isStaticBuild = useMemo(() => {
69
+ return typeof process !== 'undefined' && process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
70
+ }, []);
71
+
72
+ const push = useCallback((href: string, options?: { scroll?: boolean }) => {
73
+ if (basePath) {
74
+ // App Router doesn't handle basePath automatically, use window.location
75
+ window.location.href = withBasePath(href, basePath);
76
+ } else {
77
+ // No basePath configured, use standard router
78
+ router.push(href, options);
79
+ }
80
+ }, [router, basePath]);
81
+
82
+ const replace = useCallback((href: string, options?: { scroll?: boolean }) => {
83
+ if (basePath) {
84
+ // App Router doesn't handle basePath automatically, use window.location
85
+ window.location.replace(withBasePath(href, basePath));
86
+ } else {
87
+ // No basePath configured, use standard router
88
+ router.replace(href, options);
89
+ }
90
+ }, [router, basePath]);
91
+
92
+ /**
93
+ * Hard push - always uses window.location.href for full page reload
94
+ *
95
+ * Use this for auth redirects where React contexts need to reinitialize.
96
+ * Unlike push(), this ALWAYS triggers a full page reload, ensuring all
97
+ * contexts are reinitialized with fresh state.
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * // After successful login - contexts need to reload with new auth state
102
+ * router.hardPush('/dashboard');
103
+ * ```
104
+ */
105
+ const hardPush = useCallback((href: string) => {
106
+ window.location.href = withBasePath(href, basePath);
107
+ }, [basePath]);
108
+
109
+ /**
110
+ * Hard replace - always uses window.location.replace for full page reload
111
+ *
112
+ * Same as hardPush but replaces current history entry.
113
+ * Use for auth redirects where you don't want back button to return to login.
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * // After logout - replace so back button doesn't go to protected page
118
+ * router.hardReplace('/auth');
119
+ * ```
120
+ */
121
+ const hardReplace = useCallback((href: string) => {
122
+ window.location.replace(withBasePath(href, basePath));
123
+ }, [basePath]);
124
+
125
+ const prefetch = useCallback((href: string) => {
126
+ // Prefetch doesn't need basePath handling, Next.js handles it
127
+ router.prefetch(href);
128
+ }, [router]);
129
+
130
+ const back = useCallback(() => {
131
+ router.back();
132
+ }, [router]);
133
+
134
+ const forward = useCallback(() => {
135
+ router.forward();
136
+ }, [router]);
137
+
138
+ const refresh = useCallback(() => {
139
+ router.refresh();
140
+ }, [router]);
141
+
142
+ return {
143
+ push,
144
+ replace,
145
+ hardPush,
146
+ hardReplace,
147
+ prefetch,
148
+ back,
149
+ forward,
150
+ refresh,
151
+ };
152
+ }
153
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useCallback, useState } from 'react';
4
4
 
5
- import { useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
5
+ import { useCfgRouter } from './useCfgRouter';
6
6
 
7
7
  import { apiAccounts } from '../../clients';
8
8
  import { Analytics, AnalyticsCategory, AnalyticsEvent } from '../utils/analytics';
@@ -0,0 +1,73 @@
1
+ /**
2
+ * useQueryParams Hook
3
+ *
4
+ * Safe hook to access URL query parameters without requiring Suspense boundary.
5
+ * Works on client-side only, returns empty URLSearchParams during SSR/prerendering.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const params = useQueryParams();
10
+ * const flow = params.get('flow');
11
+ * const hasFlow = params.has('flow');
12
+ * const allTags = params.getAll('tags');
13
+ * ```
14
+ */
15
+
16
+ 'use client';
17
+
18
+ import { usePathname } from 'next/navigation';
19
+ import { useEffect, useRef, useState } from 'react';
20
+
21
+ /**
22
+ * Hook to safely access URL query parameters without useSearchParams()
23
+ *
24
+ * This hook reads query parameters directly from window.location.search,
25
+ * avoiding the need for Suspense boundaries that useSearchParams() requires.
26
+ *
27
+ * Automatically updates when URL changes (navigation, back/forward, etc.)
28
+ * Uses pathname from Next.js to detect route changes and polls for query param changes.
29
+ *
30
+ * Returns a URLSearchParams object with get(), getAll(), has(), etc.
31
+ *
32
+ * @returns URLSearchParams object (empty during SSR)
33
+ */
34
+ export function useQueryParams(): URLSearchParams {
35
+ const pathname = usePathname();
36
+ const [queryParams, setQueryParams] = useState<URLSearchParams>(() => {
37
+ if (typeof window === 'undefined') {
38
+ return new URLSearchParams();
39
+ }
40
+ return new URLSearchParams(window.location.search);
41
+ });
42
+ const lastSearchRef = useRef<string>('');
43
+
44
+ useEffect(() => {
45
+ if (typeof window === 'undefined') return;
46
+
47
+ const updateQueryParams = () => {
48
+ const currentSearch = window.location.search;
49
+ if (currentSearch !== lastSearchRef.current) {
50
+ lastSearchRef.current = currentSearch;
51
+ setQueryParams(new URLSearchParams(currentSearch));
52
+ }
53
+ };
54
+
55
+ // Update when pathname changes (Next.js navigation)
56
+ updateQueryParams();
57
+
58
+ // Listen to popstate (back/forward navigation)
59
+ window.addEventListener('popstate', updateQueryParams);
60
+
61
+ // Poll for query param changes (for router.push with same pathname)
62
+ // This handles cases where Next.js router.push updates query params without changing pathname
63
+ const intervalId = setInterval(updateQueryParams, 100);
64
+
65
+ return () => {
66
+ window.removeEventListener('popstate', updateQueryParams);
67
+ clearInterval(intervalId);
68
+ };
69
+ }, [pathname]);
70
+
71
+ return queryParams;
72
+ }
73
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useCallback, useState } from 'react';
4
4
 
5
- import { useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
5
+ import { useCfgRouter } from './useCfgRouter';
6
6
 
7
7
  import { apiAccounts, apiTotp } from '../../clients';
8
8
  import { Analytics, AnalyticsCategory, AnalyticsEvent } from '../utils/analytics';