@djangocfg/layouts 2.1.597 → 2.1.599
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 +12 -12
- package/src/layouts/SettingsLayout/SettingsDialog.tsx +2 -0
- package/src/layouts/SettingsLayout/hooks/useGuestIntentRedirect.ts +31 -0
- package/src/layouts/SettingsLayout/intent/parseSettingsHash.test.ts +11 -1
- package/src/layouts/SettingsLayout/intent/parseSettingsHash.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.599",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -91,11 +91,11 @@
|
|
|
91
91
|
"test": "vitest run"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
|
-
"@djangocfg/analytics": "^2.1.
|
|
95
|
-
"@djangocfg/api": "^2.1.
|
|
96
|
-
"@djangocfg/devtools": "^2.1.
|
|
97
|
-
"@djangocfg/i18n": "^2.1.
|
|
98
|
-
"@djangocfg/ui-core": "^2.1.
|
|
94
|
+
"@djangocfg/analytics": "^2.1.599",
|
|
95
|
+
"@djangocfg/api": "^2.1.599",
|
|
96
|
+
"@djangocfg/devtools": "^2.1.599",
|
|
97
|
+
"@djangocfg/i18n": "^2.1.599",
|
|
98
|
+
"@djangocfg/ui-core": "^2.1.599",
|
|
99
99
|
"@hookform/resolvers": "^5.2.2",
|
|
100
100
|
"consola": "^3.4.2",
|
|
101
101
|
"lucide-react": "^0.545.0",
|
|
@@ -125,12 +125,12 @@
|
|
|
125
125
|
"uuid": "^11.1.1"
|
|
126
126
|
},
|
|
127
127
|
"devDependencies": {
|
|
128
|
-
"@djangocfg/analytics": "^2.1.
|
|
129
|
-
"@djangocfg/api": "^2.1.
|
|
130
|
-
"@djangocfg/devtools": "^2.1.
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
132
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
133
|
-
"@djangocfg/ui-core": "^2.1.
|
|
128
|
+
"@djangocfg/analytics": "^2.1.599",
|
|
129
|
+
"@djangocfg/api": "^2.1.599",
|
|
130
|
+
"@djangocfg/devtools": "^2.1.599",
|
|
131
|
+
"@djangocfg/i18n": "^2.1.599",
|
|
132
|
+
"@djangocfg/typescript-config": "^2.1.599",
|
|
133
|
+
"@djangocfg/ui-core": "^2.1.599",
|
|
134
134
|
"@storybook/react-vite": "^10.5.0",
|
|
135
135
|
"@types/node": "^25.9.5",
|
|
136
136
|
"@types/react": "19.2.15",
|
|
@@ -13,6 +13,7 @@ import { useIsMobile } from '@djangocfg/ui-core/hooks';
|
|
|
13
13
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
14
14
|
|
|
15
15
|
import { SettingsForm } from './SettingsForm';
|
|
16
|
+
import { useGuestIntentRedirect } from './hooks/useGuestIntentRedirect';
|
|
16
17
|
import { useSettingsUrl } from './hooks/useSettingsUrl';
|
|
17
18
|
import { useSettingsCopy } from './i18n';
|
|
18
19
|
import { buildBuiltinSections } from './sections';
|
|
@@ -77,6 +78,7 @@ export const SettingsDialog: React.FC<SettingsDialogProps> = ({
|
|
|
77
78
|
);
|
|
78
79
|
|
|
79
80
|
useSettingsUrl({ urlKey, enabled: syncUrl && isAuthenticated, resolveSection });
|
|
81
|
+
useGuestIntentRedirect(urlKey ?? 'settings', syncUrl !== false);
|
|
80
82
|
|
|
81
83
|
const resolvedTitle = title ?? copy.nav.title;
|
|
82
84
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useGuestIntentRedirect — a guest who arrives on `#settings/<section>?<intent>` is sent to sign-in and brought back.
|
|
5
|
+
*
|
|
6
|
+
* The dialog only opens for a signed-in user, so without this an assistant's consent link (`#settings/mcp?request=…`)
|
|
7
|
+
* lands on a page where nothing happens. The full address, fragment included, is saved as the post-sign-in
|
|
8
|
+
* destination, so the dialog reads the same intent once the session exists. A plain `#settings` (no intent) is left
|
|
9
|
+
* alone: a guest opening it has nothing pending.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useEffect, useRef } from 'react';
|
|
13
|
+
|
|
14
|
+
import { useAuth, useCfgRouter } from '@djangocfg/api/auth';
|
|
15
|
+
|
|
16
|
+
import { carriesIntent } from '../intent/parseSettingsHash';
|
|
17
|
+
|
|
18
|
+
export function useGuestIntentRedirect(urlKey: string, enabled: boolean): void {
|
|
19
|
+
const { isAuthenticated, isLoading, saveRedirectUrl, routes } = useAuth();
|
|
20
|
+
const router = useCfgRouter();
|
|
21
|
+
const sent = useRef(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!enabled || isLoading || isAuthenticated || sent.current) return;
|
|
25
|
+
if (!carriesIntent(window.location.hash.slice(1), urlKey)) return;
|
|
26
|
+
sent.current = true;
|
|
27
|
+
const { pathname, search, hash } = window.location;
|
|
28
|
+
saveRedirectUrl(`${pathname}${search}${hash}`);
|
|
29
|
+
router.push(routes.auth);
|
|
30
|
+
}, [enabled, isLoading, isAuthenticated, saveRedirectUrl, router, routes.auth, urlKey]);
|
|
31
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { buildSettingsHash, parseSettingsHash, sectionToOpen } from './parseSettingsHash';
|
|
3
|
+
import { buildSettingsHash, carriesIntent, parseSettingsHash, sectionToOpen } from './parseSettingsHash';
|
|
4
4
|
|
|
5
5
|
describe('parseSettingsHash', () => {
|
|
6
6
|
it('is closed for an empty or foreign fragment', () => {
|
|
@@ -52,3 +52,13 @@ describe('sectionToOpen', () => {
|
|
|
52
52
|
expect(sectionToOpen(null, null)).toBeNull();
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
describe('carriesIntent', () => {
|
|
57
|
+
it('is true only for a settings link with a query', () => {
|
|
58
|
+
expect(carriesIntent('settings/mcp?request=abc&client=Claude', 'settings')).toBe(true);
|
|
59
|
+
expect(carriesIntent('settings/mcp', 'settings')).toBe(false);
|
|
60
|
+
expect(carriesIntent('settings', 'settings')).toBe(false);
|
|
61
|
+
expect(carriesIntent('other/mcp?request=abc', 'settings')).toBe(false);
|
|
62
|
+
expect(carriesIntent('', 'settings')).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -53,3 +53,9 @@ export function buildSettingsHash(urlKey: string, section: string | null): strin
|
|
|
53
53
|
export function sectionToOpen(requested: string | null, resolved: string | null): string | null {
|
|
54
54
|
return resolved ?? requested;
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/** True when the fragment is a Settings deep link that carries an arrival intent (`#settings/mcp?request=…`). */
|
|
58
|
+
export function carriesIntent(fragment: string, urlKey: string): boolean {
|
|
59
|
+
const parsed = parseSettingsHash(fragment, urlKey);
|
|
60
|
+
return parsed.open && Object.keys(parsed.params).length > 0;
|
|
61
|
+
}
|