@djangocfg/layouts 1.4.30 → 2.0.2
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/README.md +277 -18
- package/package.json +15 -24
- package/src/auth/context/AuthContext.tsx +5 -5
- package/src/auth/hooks/useAuthGuard.ts +1 -1
- package/src/auth/hooks/useAutoAuth.ts +8 -7
- package/src/components/ErrorBoundary.tsx +78 -0
- package/src/components/JsonLd.tsx +31 -0
- package/src/components/LucideIcon.tsx +91 -0
- package/src/components/PageProgress.tsx +127 -0
- package/src/components/Suspense.tsx +29 -0
- package/src/{layouts/AppLayout/components → components}/UpdateNotifier/UpdateNotifier.tsx +56 -49
- package/src/components/index.ts +10 -0
- package/src/index.ts +25 -7
- package/src/layouts/AdminLayout/AdminLayout.tsx +46 -0
- package/src/layouts/AdminLayout/index.ts +7 -0
- package/src/layouts/AppLayout/AppLayout.tsx +278 -326
- package/src/layouts/AppLayout/index.ts +2 -39
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthContext.tsx +3 -2
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthHelp.tsx +1 -0
- package/src/layouts/AuthLayout/AuthLayout.tsx +61 -0
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/IdentifierForm.tsx +47 -34
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/OTPForm.tsx +2 -3
- package/src/layouts/AuthLayout/index.ts +24 -0
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/types.ts +1 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +144 -0
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +32 -0
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +57 -0
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +141 -0
- package/src/layouts/PrivateLayout/components/index.ts +8 -0
- package/src/layouts/PrivateLayout/index.ts +7 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +15 -7
- package/src/layouts/PublicLayout/PublicLayout.tsx +121 -0
- package/src/layouts/PublicLayout/components/PublicFooter.tsx +190 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +117 -0
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +101 -0
- package/src/layouts/PublicLayout/components/index.ts +8 -0
- package/src/layouts/PublicLayout/index.ts +7 -0
- package/src/layouts/_components/UserMenu.tsx +160 -0
- package/src/layouts/_components/index.ts +7 -0
- package/src/layouts/index.ts +15 -8
- package/src/snippets/Analytics/AnalyticsProvider.tsx +8 -4
- package/src/snippets/Analytics/useAnalytics.ts +11 -21
- package/src/snippets/Chat/ChatWidget.tsx +4 -4
- package/src/snippets/ContactForm/ContactFormProvider.tsx +32 -19
- package/src/snippets/ContactForm/ContactPage.tsx +2 -4
- package/src/snippets/ContactForm/types.ts +3 -2
- package/src/snippets/index.ts +0 -1
- package/src/layouts/AppLayout/README.md +0 -204
- package/src/layouts/AppLayout/SUMMARY.md +0 -240
- package/src/layouts/AppLayout/USAGE.md +0 -312
- package/src/layouts/AppLayout/components/ErrorBoundary.tsx +0 -112
- package/src/layouts/AppLayout/components/PageProgress.tsx +0 -123
- package/src/layouts/AppLayout/components/Seo.tsx +0 -171
- package/src/layouts/AppLayout/components/UserMenu.tsx +0 -385
- package/src/layouts/AppLayout/components/index.ts +0 -11
- package/src/layouts/AppLayout/context/AppContext.tsx +0 -151
- package/src/layouts/AppLayout/context/index.ts +0 -5
- package/src/layouts/AppLayout/hooks/index.ts +0 -8
- package/src/layouts/AppLayout/hooks/useLayoutMode.ts +0 -26
- package/src/layouts/AppLayout/hooks/useNavigation.ts +0 -51
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +0 -224
- package/src/layouts/AppLayout/layouts/AdminLayout/README.md +0 -409
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +0 -98
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +0 -149
- package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +0 -146
- package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +0 -3
- package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +0 -48
- package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +0 -2
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/index.ts +0 -6
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +0 -279
- package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +0 -24
- package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +0 -1
- package/src/layouts/AppLayout/layouts/AdminLayout/types/index.ts +0 -45
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +0 -41
- package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +0 -15
- package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +0 -82
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +0 -62
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +0 -89
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +0 -181
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +0 -9
- package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +0 -5
- package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +0 -44
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +0 -242
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileDrawer.tsx +0 -150
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +0 -169
- package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +0 -5
- package/src/layouts/AppLayout/layouts/index.ts +0 -7
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +0 -80
- package/src/layouts/AppLayout/providers/index.ts +0 -5
- package/src/layouts/AppLayout/types/config.ts +0 -79
- package/src/layouts/AppLayout/types/index.ts +0 -11
- package/src/layouts/AppLayout/types/layout.ts +0 -54
- package/src/layouts/AppLayout/types/navigation.ts +0 -43
- package/src/layouts/AppLayout/types/page.ts +0 -80
- package/src/layouts/AppLayout/types/routes.ts +0 -43
- package/src/layouts/AppLayout/utils/index.ts +0 -5
- package/src/layouts/AppLayout/utils/routeDetection.ts +0 -31
- package/src/layouts/ErrorLayout/ErrorLayout.tsx +0 -173
- package/src/layouts/ErrorLayout/errorConfig.tsx +0 -152
- package/src/layouts/ErrorLayout/index.ts +0 -8
- package/src/layouts/SimpleLayout/SimpleLayout.tsx +0 -72
- package/src/layouts/SimpleLayout/index.ts +0 -3
- package/src/snippets/VideoPlayer/README.md +0 -238
- package/src/snippets/VideoPlayer/VideoControls.tsx +0 -137
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +0 -248
- package/src/snippets/VideoPlayer/index.ts +0 -8
- package/src/snippets/VideoPlayer/types.ts +0 -61
- package/src/types/index.ts +0 -2
- package/src/types/pageConfig.ts +0 -100
- /package/src/{validation → components/ErrorsTracker}/README.md +0 -0
- /package/src/{validation → components/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/components/ErrorToast.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/hooks.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/index.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/types.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/utils/curl-generator.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/utils/formatters.ts +0 -0
- /package/src/{layouts/AppLayout/components → components}/UpdateNotifier/index.ts +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LucideIcon Component
|
|
3
|
+
*
|
|
4
|
+
* Smart component for rendering Lucide icons
|
|
5
|
+
* Supports both string names and icon components
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // By name (string)
|
|
10
|
+
* <LucideIcon icon="CloudLightning" className="w-4 h-4" />
|
|
11
|
+
*
|
|
12
|
+
* // By component
|
|
13
|
+
* import { CloudLightning } from 'lucide-react';
|
|
14
|
+
* <LucideIcon icon={CloudLightning} className="w-4 h-4" />
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use client';
|
|
19
|
+
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import * as LucideIcons from 'lucide-react';
|
|
22
|
+
import type { LucideIcon as LucideIconType } from 'lucide-react';
|
|
23
|
+
import { cn } from '@djangocfg/ui';
|
|
24
|
+
|
|
25
|
+
export interface LucideIconProps extends Omit<React.SVGProps<SVGSVGElement>, 'children'> {
|
|
26
|
+
/**
|
|
27
|
+
* Icon name (string) or icon component (LucideIcon)
|
|
28
|
+
* If string, will be resolved from lucide-react
|
|
29
|
+
* If component, will be used directly
|
|
30
|
+
*/
|
|
31
|
+
icon?: LucideIconType | string;
|
|
32
|
+
/**
|
|
33
|
+
* Fallback icon if name not found
|
|
34
|
+
* @default CloudLightning
|
|
35
|
+
*/
|
|
36
|
+
fallback?: LucideIconType;
|
|
37
|
+
/**
|
|
38
|
+
* Additional CSS classes
|
|
39
|
+
*/
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get Lucide icon by name or return the icon component
|
|
45
|
+
* @param icon - Icon name (string) or LucideIcon component
|
|
46
|
+
* @param fallback - Fallback icon if name not found (default: CloudLightning)
|
|
47
|
+
* @returns LucideIcon component
|
|
48
|
+
*/
|
|
49
|
+
function getLucideIcon(
|
|
50
|
+
icon: LucideIconType | string | undefined,
|
|
51
|
+
fallback: LucideIconType = LucideIcons.CloudLightning
|
|
52
|
+
): LucideIconType {
|
|
53
|
+
if (!icon) {
|
|
54
|
+
return fallback;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof icon === 'string') {
|
|
58
|
+
const IconComponent = LucideIcons[icon as keyof typeof LucideIcons] as LucideIconType | undefined;
|
|
59
|
+
return IconComponent || fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return icon;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* LucideIcon Component
|
|
67
|
+
*
|
|
68
|
+
* Renders a Lucide icon either by name (string) or by component reference.
|
|
69
|
+
* Automatically resolves icon names from lucide-react package.
|
|
70
|
+
*
|
|
71
|
+
* Usage:
|
|
72
|
+
* ```tsx
|
|
73
|
+
* // By name (string)
|
|
74
|
+
* <LucideIcon icon="CloudLightning" className="w-4 h-4" />
|
|
75
|
+
*
|
|
76
|
+
* // By component
|
|
77
|
+
* import { CloudLightning } from 'lucide-react';
|
|
78
|
+
* <LucideIcon icon={CloudLightning} className="w-4 h-4" />
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function LucideIcon({
|
|
82
|
+
icon,
|
|
83
|
+
fallback = LucideIcons.CloudLightning,
|
|
84
|
+
className,
|
|
85
|
+
...props
|
|
86
|
+
}: LucideIconProps) {
|
|
87
|
+
const IconComponent = getLucideIcon(icon, fallback);
|
|
88
|
+
|
|
89
|
+
return <IconComponent className={cn(className)} {...props} />;
|
|
90
|
+
}
|
|
91
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PageProgress - Loading progress bar
|
|
3
|
+
*
|
|
4
|
+
* Shows a progress bar at the top of the page during route transitions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import { usePathname } from 'next/navigation';
|
|
10
|
+
import { useEffect, useRef, useState } from 'react';
|
|
11
|
+
|
|
12
|
+
export function PageProgress() {
|
|
13
|
+
const pathname = usePathname();
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
const [progress, setProgress] = useState(0);
|
|
16
|
+
const [mounted, setMounted] = useState(false);
|
|
17
|
+
const progressTimer = useRef<NodeJS.Timeout | null>(null);
|
|
18
|
+
const prevPathname = useRef<string | null>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
setMounted(true);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
// Simulate realistic progress
|
|
25
|
+
const startFakeProgress = () => {
|
|
26
|
+
// Clear any existing timer
|
|
27
|
+
if (progressTimer.current) {
|
|
28
|
+
clearInterval(progressTimer.current);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setProgress(0);
|
|
32
|
+
|
|
33
|
+
// Quickly go to 20% to show immediate feedback
|
|
34
|
+
setTimeout(() => setProgress(20), 50);
|
|
35
|
+
|
|
36
|
+
// Then slowly increase to 90% (never reach 100% until actually complete)
|
|
37
|
+
progressTimer.current = setInterval(() => {
|
|
38
|
+
setProgress((prevProgress) => {
|
|
39
|
+
if (prevProgress >= 90) {
|
|
40
|
+
if (progressTimer.current) {
|
|
41
|
+
clearInterval(progressTimer.current);
|
|
42
|
+
}
|
|
43
|
+
return 90;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Slow down as we get closer to 90%
|
|
47
|
+
const increment = 90 - prevProgress;
|
|
48
|
+
return prevProgress + (increment / 10);
|
|
49
|
+
});
|
|
50
|
+
}, 300);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const completeProgress = () => {
|
|
54
|
+
// Clear any existing timer
|
|
55
|
+
if (progressTimer.current) {
|
|
56
|
+
clearInterval(progressTimer.current);
|
|
57
|
+
progressTimer.current = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Jump to 100% and then hide after showing completion
|
|
61
|
+
setProgress(100);
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
setLoading(false);
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
setProgress(0);
|
|
66
|
+
}, 300); // Wait for fade out animation
|
|
67
|
+
}, 500); // Show 100% for half a second
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Track pathname changes (App Router equivalent of routeChangeStart/Complete)
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
// Skip on initial mount
|
|
73
|
+
if (prevPathname.current === null) {
|
|
74
|
+
prevPathname.current = pathname;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Only trigger if pathname actually changed
|
|
79
|
+
if (prevPathname.current !== pathname) {
|
|
80
|
+
setLoading(true);
|
|
81
|
+
startFakeProgress();
|
|
82
|
+
|
|
83
|
+
// Complete progress after a short delay (simulating route change)
|
|
84
|
+
const timeout = setTimeout(() => {
|
|
85
|
+
completeProgress();
|
|
86
|
+
prevPathname.current = pathname;
|
|
87
|
+
}, 100);
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
if (progressTimer.current) {
|
|
92
|
+
clearInterval(progressTimer.current);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}, [pathname]);
|
|
97
|
+
|
|
98
|
+
if (!mounted) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
data-page-progress="root"
|
|
105
|
+
data-loading={loading}
|
|
106
|
+
data-progress={progress}
|
|
107
|
+
className={`fixed top-0 left-0 w-full transition-opacity duration-300 ${
|
|
108
|
+
loading ? 'opacity-100' : 'opacity-0'
|
|
109
|
+
}`}
|
|
110
|
+
style={{
|
|
111
|
+
zIndex: 99999,
|
|
112
|
+
height: '3px',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<div
|
|
116
|
+
className="h-full transition-all duration-200 ease-linear"
|
|
117
|
+
style={{
|
|
118
|
+
width: `${progress}%`,
|
|
119
|
+
background: 'linear-gradient(90deg, #3b82f6 0%, #60a5fa 50%, #3b82f6 100%)',
|
|
120
|
+
boxShadow: '0 0 10px rgba(59, 130, 246, 0.6), 0 0 20px rgba(59, 130, 246, 0.4), 0 0 30px rgba(59, 130, 246, 0.2)',
|
|
121
|
+
filter: 'drop-shadow(0 0 8px rgba(59, 130, 246, 0.8))',
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suspense Wrapper Component
|
|
3
|
+
*
|
|
4
|
+
* Reusable Suspense component with consistent loading fallback
|
|
5
|
+
* Used for wrapping layouts that may use useSearchParams or other async hooks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import { Suspense as ReactSuspense, ReactNode } from 'react';
|
|
11
|
+
|
|
12
|
+
interface SuspenseProps {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
fallback?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const defaultFallback = (
|
|
18
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
19
|
+
<div className="text-center">
|
|
20
|
+
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" />
|
|
21
|
+
<p className="mt-4 text-sm text-muted-foreground">Loading...</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export function Suspense({ children, fallback = defaultFallback }: SuspenseProps) {
|
|
27
|
+
return <ReactSuspense fallback={fallback}>{children}</ReactSuspense>;
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
'use client';
|
|
10
10
|
|
|
11
|
-
import React, { useEffect, useState } from 'react';
|
|
11
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
12
12
|
import consola from 'consola';
|
|
13
13
|
import { toast } from '@djangocfg/ui/hooks';
|
|
14
|
+
import { useLocalStorage } from '@djangocfg/ui/hooks';
|
|
14
15
|
|
|
15
16
|
const PACKAGE_NAME = '@djangocfg/layouts';
|
|
16
17
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
@@ -22,16 +23,34 @@ interface UpdateCheckCache {
|
|
|
22
23
|
dismissed: boolean;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
const defaultCache: UpdateCheckCache = {
|
|
27
|
+
lastCheck: 0,
|
|
28
|
+
latestVersion: '',
|
|
29
|
+
dismissed: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
25
32
|
export interface UpdateNotifierProps {
|
|
26
33
|
/**
|
|
27
34
|
* Enable update notifications
|
|
28
35
|
* @default false
|
|
29
36
|
*/
|
|
30
37
|
enabled?: boolean;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get current package version from package.json
|
|
42
|
+
* Uses require in runtime to avoid TypeScript rootDir issues
|
|
43
|
+
*/
|
|
44
|
+
function getCurrentVersion(): string | null {
|
|
45
|
+
try {
|
|
46
|
+
// Use require in runtime (works in both dev and build)
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
|
+
const packageJson = require('../../../package.json');
|
|
49
|
+
return packageJson.version || null;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
consola.warn('[UpdateNotifier] Failed to load package.json:', error);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
/**
|
|
@@ -69,53 +88,36 @@ async function fetchLatestVersion(): Promise<string | null> {
|
|
|
69
88
|
}
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
|
|
73
|
-
* Get cached update check data
|
|
74
|
-
*/
|
|
75
|
-
function getCache(): UpdateCheckCache | null {
|
|
76
|
-
if (typeof window === 'undefined') return null;
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const cached = localStorage.getItem(CACHE_KEY);
|
|
80
|
-
if (!cached) return null;
|
|
81
|
-
|
|
82
|
-
const data: UpdateCheckCache = JSON.parse(cached);
|
|
83
|
-
return data;
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Save update check data to cache
|
|
91
|
-
*/
|
|
92
|
-
function setCache(data: UpdateCheckCache): void {
|
|
93
|
-
if (typeof window === 'undefined') return;
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
|
97
|
-
} catch (error) {
|
|
98
|
-
consola.warn('[UpdateNotifier] Failed to cache update check:', error);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function UpdateNotifier({ enabled = false, currentVersion }: UpdateNotifierProps) {
|
|
91
|
+
export function UpdateNotifier({ enabled = false }: UpdateNotifierProps) {
|
|
103
92
|
const [checked, setChecked] = useState(false);
|
|
93
|
+
const [cache, setCache] = useLocalStorage<UpdateCheckCache | null>(CACHE_KEY, null);
|
|
94
|
+
const cacheRef = useRef(cache);
|
|
95
|
+
|
|
96
|
+
// Keep cacheRef in sync with cache
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
cacheRef.current = cache;
|
|
99
|
+
}, [cache]);
|
|
104
100
|
|
|
105
101
|
useEffect(() => {
|
|
106
102
|
if (!enabled || checked || typeof window === 'undefined') return;
|
|
107
|
-
if (!currentVersion) return;
|
|
108
103
|
|
|
109
104
|
const checkForUpdates = async () => {
|
|
110
|
-
//
|
|
111
|
-
const
|
|
105
|
+
// Get current version from package.json
|
|
106
|
+
const currentVersion = getCurrentVersion();
|
|
107
|
+
if (!currentVersion) {
|
|
108
|
+
setChecked(true);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check cache first (use ref to get latest value)
|
|
112
113
|
const now = Date.now();
|
|
114
|
+
const cachedData = cacheRef.current || defaultCache;
|
|
113
115
|
|
|
114
116
|
// If we checked recently, skip
|
|
115
|
-
if (
|
|
117
|
+
if (cachedData && cachedData.lastCheck > 0 && (now - cachedData.lastCheck) < CHECK_INTERVAL_MS) {
|
|
116
118
|
// Show notification if there's an update and it wasn't dismissed
|
|
117
|
-
if (
|
|
118
|
-
showUpdateNotification(currentVersion,
|
|
119
|
+
if (cachedData.latestVersion && !cachedData.dismissed && isNewerVersion(currentVersion, cachedData.latestVersion)) {
|
|
120
|
+
showUpdateNotification(currentVersion, cachedData.latestVersion, setCache);
|
|
119
121
|
}
|
|
120
122
|
setChecked(true);
|
|
121
123
|
return;
|
|
@@ -138,7 +140,7 @@ export function UpdateNotifier({ enabled = false, currentVersion }: UpdateNotifi
|
|
|
138
140
|
|
|
139
141
|
// Show notification if newer version available
|
|
140
142
|
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
141
|
-
showUpdateNotification(currentVersion, latestVersion);
|
|
143
|
+
showUpdateNotification(currentVersion, latestVersion, setCache);
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
setChecked(true);
|
|
@@ -148,7 +150,8 @@ export function UpdateNotifier({ enabled = false, currentVersion }: UpdateNotifi
|
|
|
148
150
|
const timer = setTimeout(checkForUpdates, 2000);
|
|
149
151
|
|
|
150
152
|
return () => clearTimeout(timer);
|
|
151
|
-
|
|
153
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
154
|
+
}, [enabled, checked]);
|
|
152
155
|
|
|
153
156
|
return null; // This component doesn't render anything
|
|
154
157
|
}
|
|
@@ -156,7 +159,11 @@ export function UpdateNotifier({ enabled = false, currentVersion }: UpdateNotifi
|
|
|
156
159
|
/**
|
|
157
160
|
* Show update notification toast
|
|
158
161
|
*/
|
|
159
|
-
function showUpdateNotification(
|
|
162
|
+
function showUpdateNotification(
|
|
163
|
+
currentVersion: string,
|
|
164
|
+
latestVersion: string,
|
|
165
|
+
setCache: (value: UpdateCheckCache | null | ((val: UpdateCheckCache | null) => UpdateCheckCache | null)) => void
|
|
166
|
+
) {
|
|
160
167
|
toast({
|
|
161
168
|
title: `📦 Update Available`,
|
|
162
169
|
description: `New version ${latestVersion} of @djangocfg packages is available. You're using ${currentVersion}. Run: pnpm update @djangocfg/layouts@latest`,
|
|
@@ -164,8 +171,8 @@ function showUpdateNotification(currentVersion: string, latestVersion: string) {
|
|
|
164
171
|
});
|
|
165
172
|
|
|
166
173
|
// Mark as dismissed in cache after showing
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
174
|
+
setCache((prev) => {
|
|
175
|
+
if (!prev) return null;
|
|
176
|
+
return { ...prev, dismissed: true };
|
|
177
|
+
});
|
|
171
178
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Components exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { JsonLd } from './JsonLd';
|
|
6
|
+
export { LucideIcon } from './LucideIcon';
|
|
7
|
+
export type { LucideIconProps } from './LucideIcon';
|
|
8
|
+
export { PageProgress } from './PageProgress';
|
|
9
|
+
|
|
10
|
+
export { Suspense } from './Suspense';
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @djangocfg/layouts
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Simple, straightforward layout components for Next.js
|
|
5
|
+
* Import and use directly with props - no complex configs needed!
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { PublicLayout, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
|
|
10
|
+
*
|
|
11
|
+
* // Public page
|
|
12
|
+
* <PublicLayout logo="/logo.svg" siteName="My App" navigation={navItems}>
|
|
13
|
+
* {children}
|
|
14
|
+
* </PublicLayout>
|
|
15
|
+
*
|
|
16
|
+
* // Private page
|
|
17
|
+
* <PrivateLayout sidebar={{ items: menuItems }} header={{ title: 'Dashboard' }}>
|
|
18
|
+
* {children}
|
|
19
|
+
* </PrivateLayout>
|
|
20
|
+
*
|
|
21
|
+
* // Auth page
|
|
22
|
+
* <AuthLayout logo="/logo.svg" title="Sign In">
|
|
23
|
+
* <LoginForm />
|
|
24
|
+
* </AuthLayout>
|
|
25
|
+
* ```
|
|
5
26
|
*/
|
|
6
27
|
|
|
7
28
|
// Auth system
|
|
@@ -10,12 +31,9 @@ export * from './auth';
|
|
|
10
31
|
// Layout components
|
|
11
32
|
export * from './layouts';
|
|
12
33
|
|
|
13
|
-
// Types
|
|
14
|
-
export * from './types';
|
|
15
|
-
|
|
16
34
|
// Snippets - Reusable UI components (includes Analytics)
|
|
17
35
|
export * from './snippets';
|
|
18
36
|
|
|
19
|
-
//
|
|
20
|
-
export * from './
|
|
37
|
+
// Components (includes ErrorTrackingProvider)
|
|
38
|
+
export * from './components';
|
|
21
39
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Layout
|
|
3
|
+
*
|
|
4
|
+
* Layout for admin dashboard pages
|
|
5
|
+
* Import and use directly with props - no complex configs needed!
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { AdminLayout } from '@djangocfg/layouts';
|
|
10
|
+
*
|
|
11
|
+
* <AdminLayout
|
|
12
|
+
* sidebar={{
|
|
13
|
+
* items: [
|
|
14
|
+
* { label: 'Overview', href: '/admin', icon: 'LayoutDashboard' },
|
|
15
|
+
* { label: 'Users', href: '/admin/users', icon: 'Users' }
|
|
16
|
+
* ]
|
|
17
|
+
* }}
|
|
18
|
+
* header={{
|
|
19
|
+
* title: 'Admin Dashboard',
|
|
20
|
+
* profilePath: '/profile' // Optional
|
|
21
|
+
* }}
|
|
22
|
+
* >
|
|
23
|
+
* {children}
|
|
24
|
+
* </AdminLayout>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
'use client';
|
|
29
|
+
|
|
30
|
+
import { ReactNode } from 'react';
|
|
31
|
+
import { PrivateLayout, type PrivateLayoutProps } from '../PrivateLayout/PrivateLayout';
|
|
32
|
+
|
|
33
|
+
export interface AdminLayoutProps extends PrivateLayoutProps {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Admin Layout Component
|
|
39
|
+
*
|
|
40
|
+
* Wrapper around PrivateLayout with admin-specific defaults
|
|
41
|
+
* Same API as PrivateLayout - just a convenience export
|
|
42
|
+
*/
|
|
43
|
+
export function AdminLayout(props: AdminLayoutProps) {
|
|
44
|
+
return <PrivateLayout {...props} />;
|
|
45
|
+
}
|
|
46
|
+
|