@djangocfg/layouts 2.1.152 → 2.1.154
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 +14 -14
- package/src/components/errors/ErrorsTracker/components/ErrorToast.tsx +1 -1
- package/src/components/errors/ErrorsTracker/providers/ErrorTrackingProvider.tsx +1 -2
- package/src/components/errors/errorConfig.ts +2 -1
- package/src/layouts/AppLayout/AppLayout.tsx +1 -14
- package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +1 -1
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +1 -1
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +1 -1
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +1 -1
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +3 -2
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -1
- package/src/snippets/Analytics/useAnalytics.ts +2 -2
- package/src/snippets/PWAInstall/components/DesktopGuide.tsx +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/pathMatcher.ts +118 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.154",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -74,12 +74,12 @@
|
|
|
74
74
|
"check": "tsc --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@djangocfg/api": "^2.1.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/i18n": "^2.1.
|
|
80
|
-
"@djangocfg/ui-core": "^2.1.
|
|
81
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
82
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
77
|
+
"@djangocfg/api": "^2.1.154",
|
|
78
|
+
"@djangocfg/centrifugo": "^2.1.154",
|
|
79
|
+
"@djangocfg/i18n": "^2.1.154",
|
|
80
|
+
"@djangocfg/ui-core": "^2.1.154",
|
|
81
|
+
"@djangocfg/ui-nextjs": "^2.1.154",
|
|
82
|
+
"@djangocfg/ui-tools": "^2.1.154",
|
|
83
83
|
"@hookform/resolvers": "^5.2.2",
|
|
84
84
|
"consola": "^3.4.2",
|
|
85
85
|
"lucide-react": "^0.545.0",
|
|
@@ -102,13 +102,13 @@
|
|
|
102
102
|
"uuid": "^11.1.0"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
|
-
"@djangocfg/api": "^2.1.
|
|
106
|
-
"@djangocfg/i18n": "^2.1.
|
|
107
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
108
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
109
|
-
"@djangocfg/ui-core": "^2.1.
|
|
110
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
111
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
105
|
+
"@djangocfg/api": "^2.1.154",
|
|
106
|
+
"@djangocfg/i18n": "^2.1.154",
|
|
107
|
+
"@djangocfg/centrifugo": "^2.1.154",
|
|
108
|
+
"@djangocfg/typescript-config": "^2.1.154",
|
|
109
|
+
"@djangocfg/ui-core": "^2.1.154",
|
|
110
|
+
"@djangocfg/ui-nextjs": "^2.1.154",
|
|
111
|
+
"@djangocfg/ui-tools": "^2.1.154",
|
|
112
112
|
"@types/node": "^24.7.2",
|
|
113
113
|
"@types/react": "^19.1.0",
|
|
114
114
|
"@types/react-dom": "^19.1.0",
|
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
import type {
|
|
45
45
|
ErrorDetail,
|
|
46
46
|
StoredError,
|
|
47
|
-
ErrorTrackingConfig,
|
|
48
47
|
ValidationErrorConfig,
|
|
49
48
|
CORSErrorConfig,
|
|
50
49
|
NetworkErrorConfig,
|
|
@@ -198,7 +197,7 @@ export function ErrorTrackingProvider({
|
|
|
198
197
|
/**
|
|
199
198
|
* Clear errors by type
|
|
200
199
|
*/
|
|
201
|
-
const clearErrorsByType = useCallback((type: 'validation' | 'cors' | 'network') => {
|
|
200
|
+
const clearErrorsByType = useCallback((type: 'validation' | 'cors' | 'network' | 'centrifugo') => {
|
|
202
201
|
setErrors((prev) => prev.filter((error) => error.type !== type));
|
|
203
202
|
}, []);
|
|
204
203
|
|
|
@@ -13,7 +13,8 @@ export interface ErrorContent {
|
|
|
13
13
|
description: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
type TranslationFn = (key: any, params?: Record<string, string | number>) => string;
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Get standardized error content based on status code
|
|
@@ -36,6 +36,7 @@ import React, { ReactNode, useMemo } from 'react';
|
|
|
36
36
|
|
|
37
37
|
import { ClientOnly, Suspense } from '../../components/core';
|
|
38
38
|
import { usePathnameWithoutLocale } from '../../hooks';
|
|
39
|
+
import { matchesPath } from '../../utils/pathMatcher';
|
|
39
40
|
import { BaseApp } from './BaseApp';
|
|
40
41
|
|
|
41
42
|
import type {
|
|
@@ -52,20 +53,6 @@ import type { AuthConfig } from '@djangocfg/api/auth';
|
|
|
52
53
|
|
|
53
54
|
export type LayoutMode = 'public' | 'private' | 'admin';
|
|
54
55
|
|
|
55
|
-
/**
|
|
56
|
-
* Check if pathname matches enabledPath
|
|
57
|
-
*/
|
|
58
|
-
function matchesPath(pathname: string, enabledPath?: string | string[]): boolean {
|
|
59
|
-
if (!enabledPath) return false;
|
|
60
|
-
|
|
61
|
-
if (typeof enabledPath === 'string') {
|
|
62
|
-
return pathname === enabledPath || pathname.startsWith(enabledPath + '/');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Array of paths
|
|
66
|
-
return enabledPath.some(path => pathname === path || pathname.startsWith(path + '/'));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
56
|
/**
|
|
70
57
|
* Determine layout mode from pathname and enabledPath props
|
|
71
58
|
*/
|
|
@@ -18,7 +18,7 @@ import { cn } from '@djangocfg/ui-core/lib';
|
|
|
18
18
|
|
|
19
19
|
import { LucideIcon } from '../../../components';
|
|
20
20
|
|
|
21
|
-
import type { SidebarItem, SidebarConfig
|
|
21
|
+
import type { SidebarItem, SidebarConfig } from '../PrivateLayout';
|
|
22
22
|
|
|
23
23
|
interface PrivateSidebarProps {
|
|
24
24
|
sidebar: SidebarConfig;
|
|
@@ -45,7 +45,8 @@ interface ProfileLayoutProps {
|
|
|
45
45
|
enableDeleteAccount?: boolean;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
// Reserved for future use
|
|
49
|
+
// type EditingField = 'first_name' | 'last_name' | 'company' | 'position' | 'phone' | null;
|
|
49
50
|
|
|
50
51
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
52
|
// Editable Field Component
|
|
@@ -201,7 +202,7 @@ const ProfileContent = ({
|
|
|
201
202
|
enable2FA = false,
|
|
202
203
|
enableDeleteAccount = true,
|
|
203
204
|
}: ProfileLayoutProps) => {
|
|
204
|
-
const { user, isLoading,
|
|
205
|
+
const { user, isLoading, uploadAvatar, updateProfile } = useAuth();
|
|
205
206
|
const [isUploading, setIsUploading] = useState(false);
|
|
206
207
|
const t = useTypedT<I18nTranslations>();
|
|
207
208
|
|
|
@@ -13,7 +13,7 @@ import React, { useMemo } from 'react';
|
|
|
13
13
|
import { useAuth } from '@djangocfg/api/auth';
|
|
14
14
|
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
15
15
|
import {
|
|
16
|
-
|
|
16
|
+
Drawer, DrawerClose, DrawerContent, DrawerHeader, DrawerTitle
|
|
17
17
|
} from '@djangocfg/ui-core/components';
|
|
18
18
|
import { ThemeToggle } from '@djangocfg/ui-nextjs/theme';
|
|
19
19
|
|
|
@@ -38,7 +38,7 @@ export function PublicMobileDrawer({
|
|
|
38
38
|
navigation,
|
|
39
39
|
userMenu,
|
|
40
40
|
}: PublicMobileDrawerProps) {
|
|
41
|
-
const { isAuthenticated } = useAuth();
|
|
41
|
+
const { isAuthenticated: _isAuthenticated } = useAuth();
|
|
42
42
|
const t = useTypedT<I18nTranslations>();
|
|
43
43
|
|
|
44
44
|
const labels = useMemo(() => ({
|
|
@@ -13,6 +13,7 @@ import React, { useMemo } from 'react';
|
|
|
13
13
|
import { useAuth } from '@djangocfg/api/auth';
|
|
14
14
|
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
15
15
|
import { Button } from '@djangocfg/ui-core/components';
|
|
16
|
+
// useIsMobile is used for conditional rendering
|
|
16
17
|
import { useIsMobile } from '@djangocfg/ui-core/hooks';
|
|
17
18
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
18
19
|
import { ThemeToggle } from '@djangocfg/ui-nextjs/theme';
|
|
@@ -41,7 +42,7 @@ export function PublicNavigation({
|
|
|
41
42
|
onMobileMenuClick,
|
|
42
43
|
i18n,
|
|
43
44
|
}: PublicNavigationProps) {
|
|
44
|
-
const { isAuthenticated } = useAuth();
|
|
45
|
+
const { isAuthenticated: _isAuthenticated } = useAuth();
|
|
45
46
|
const isMobile = useIsMobile();
|
|
46
47
|
const t = useTypedT<I18nTranslations>();
|
|
47
48
|
|
|
@@ -19,7 +19,7 @@ const isProduction = process.env.NODE_ENV === 'production';
|
|
|
19
19
|
|
|
20
20
|
// Tracking state
|
|
21
21
|
let isInitialized = false;
|
|
22
|
-
let
|
|
22
|
+
let _currentTrackingId: string | undefined;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Analytics utility object for standalone usage (outside React components)
|
|
@@ -40,7 +40,7 @@ export const Analytics = {
|
|
|
40
40
|
if (!isProduction || !trackingId || isInitialized) return;
|
|
41
41
|
ReactGA.initialize(trackingId);
|
|
42
42
|
isInitialized = true;
|
|
43
|
-
|
|
43
|
+
_currentTrackingId = trackingId;
|
|
44
44
|
},
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -33,7 +33,7 @@ function getBrowserCategory(browser: {
|
|
|
33
33
|
return 'unknown';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
type TranslationFn = (key:
|
|
36
|
+
type TranslationFn = (key: any) => string;
|
|
37
37
|
|
|
38
38
|
function getBrowserSteps(category: BrowserCategory, t: TranslationFn): InstallStep[] {
|
|
39
39
|
switch (category) {
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Matcher Utility
|
|
3
|
+
*
|
|
4
|
+
* Functions for matching URL paths against patterns with glob support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Match pathname against glob pattern
|
|
9
|
+
*
|
|
10
|
+
* @param pathname - The URL pathname to match (e.g., '/projects/123/edit')
|
|
11
|
+
* @param pattern - The pattern to match against (e.g., '/projects/*/edit')
|
|
12
|
+
* @returns true if pathname matches the pattern
|
|
13
|
+
*
|
|
14
|
+
* Glob patterns:
|
|
15
|
+
* - '*' matches any single path segment
|
|
16
|
+
* - '**' matches any number of path segments (zero or more)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* matchGlobPattern('/projects/123/edit', '/projects/*/edit') // true
|
|
20
|
+
* matchGlobPattern('/projects/abc/edit', '/projects/*/edit') // true
|
|
21
|
+
* matchGlobPattern('/admin/users/1/settings', '/admin/**') // true
|
|
22
|
+
* matchGlobPattern('/projects/edit', '/projects/*/edit') // false
|
|
23
|
+
*/
|
|
24
|
+
export function matchGlobPattern(pathname: string, pattern: string): boolean {
|
|
25
|
+
// Normalize paths (remove trailing slashes)
|
|
26
|
+
const normalizedPath = pathname.replace(/\/+$/, '');
|
|
27
|
+
const normalizedPattern = pattern.replace(/\/+$/, '');
|
|
28
|
+
|
|
29
|
+
// Split into segments
|
|
30
|
+
const pathParts = normalizedPath.split('/').filter(Boolean);
|
|
31
|
+
const patternParts = normalizedPattern.split('/').filter(Boolean);
|
|
32
|
+
|
|
33
|
+
let pathIdx = 0;
|
|
34
|
+
let patternIdx = 0;
|
|
35
|
+
|
|
36
|
+
while (patternIdx < patternParts.length && pathIdx < pathParts.length) {
|
|
37
|
+
const patternPart = patternParts[patternIdx];
|
|
38
|
+
|
|
39
|
+
if (patternPart === '**') {
|
|
40
|
+
// '**' matches zero or more segments
|
|
41
|
+
// If it's the last pattern part, it matches everything remaining
|
|
42
|
+
if (patternIdx === patternParts.length - 1) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Otherwise, try to match the next pattern part against remaining path parts
|
|
46
|
+
const nextPatternPart = patternParts[patternIdx + 1];
|
|
47
|
+
while (pathIdx < pathParts.length) {
|
|
48
|
+
if (pathParts[pathIdx] === nextPatternPart || nextPatternPart === '*') {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
pathIdx++;
|
|
52
|
+
}
|
|
53
|
+
patternIdx++;
|
|
54
|
+
} else if (patternPart === '*') {
|
|
55
|
+
// '*' matches exactly one segment
|
|
56
|
+
pathIdx++;
|
|
57
|
+
patternIdx++;
|
|
58
|
+
} else {
|
|
59
|
+
// Literal match
|
|
60
|
+
if (pathParts[pathIdx] !== patternPart) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
pathIdx++;
|
|
64
|
+
patternIdx++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if we've consumed both arrays
|
|
69
|
+
// Pattern can have trailing '**' which matches empty
|
|
70
|
+
if (patternIdx < patternParts.length) {
|
|
71
|
+
// Remaining pattern parts must all be '**'
|
|
72
|
+
for (let i = patternIdx; i < patternParts.length; i++) {
|
|
73
|
+
if (patternParts[i] !== '**') {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return pathIdx === pathParts.length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if pathname matches any of the enabled paths
|
|
84
|
+
*
|
|
85
|
+
* @param pathname - The URL pathname to check
|
|
86
|
+
* @param enabledPath - Single path, array of paths, or undefined
|
|
87
|
+
* @returns true if pathname matches any enabled path
|
|
88
|
+
*
|
|
89
|
+
* Supports:
|
|
90
|
+
* - Exact match: '/dashboard' matches '/dashboard'
|
|
91
|
+
* - Prefix match: '/dashboard' matches '/dashboard/settings'
|
|
92
|
+
* - Glob patterns: '/projects/*\/edit' matches '/projects/123/edit'
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* matchesPath('/dashboard', '/dashboard') // true
|
|
96
|
+
* matchesPath('/dashboard/settings', '/dashboard') // true
|
|
97
|
+
* matchesPath('/projects/123/edit', '/projects/*\/edit') // true
|
|
98
|
+
* matchesPath('/home', ['/dashboard', '/admin']) // false
|
|
99
|
+
*/
|
|
100
|
+
export function matchesPath(pathname: string, enabledPath?: string | string[]): boolean {
|
|
101
|
+
if (!enabledPath) return false;
|
|
102
|
+
|
|
103
|
+
const matchSinglePath = (path: string): boolean => {
|
|
104
|
+
// If pattern contains glob characters, use pattern matching
|
|
105
|
+
if (path.includes('*')) {
|
|
106
|
+
return matchGlobPattern(pathname, path);
|
|
107
|
+
}
|
|
108
|
+
// Otherwise, exact or prefix match
|
|
109
|
+
return pathname === path || pathname.startsWith(path + '/');
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (typeof enabledPath === 'string') {
|
|
113
|
+
return matchSinglePath(enabledPath);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Array of paths
|
|
117
|
+
return enabledPath.some(matchSinglePath);
|
|
118
|
+
}
|