@mdguggenbichler/slugbase-core 0.0.14 → 0.0.16
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/backend/dist/routes/admin/settings.js +86 -86
- package/backend/dist/routes/admin/settings.js.map +1 -1
- package/frontend/src/App.tsx +24 -11
- package/frontend/src/components/AppSidebar.tsx +30 -20
- package/frontend/src/components/GlobalSearch.tsx +15 -14
- package/frontend/src/components/TopBar.tsx +4 -3
- package/frontend/src/components/UserDropdown.tsx +4 -3
- package/frontend/src/components/admin/AdminAI.tsx +12 -0
- package/frontend/src/contexts/AppConfigContext.tsx +7 -0
- package/frontend/src/pages/Bookmarks.tsx +4 -3
- package/frontend/src/pages/Dashboard.tsx +23 -22
- package/frontend/src/pages/Folders.tsx +4 -3
- package/frontend/src/pages/GoPreferences.tsx +3 -2
- package/frontend/src/pages/PasswordReset.tsx +5 -4
- package/frontend/src/pages/Profile.tsx +3 -2
- package/frontend/src/pages/SearchEngineGuide.tsx +3 -2
- package/frontend/src/pages/Tags.tsx +4 -3
- package/package.json +1 -1
|
@@ -58,6 +58,92 @@ router.get('/', async (req, res) => {
|
|
|
58
58
|
res.status(500).json({ error: error.message });
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
|
+
/**
|
|
62
|
+
* @swagger
|
|
63
|
+
* /api/admin/settings/ai:
|
|
64
|
+
* get:
|
|
65
|
+
* summary: Get AI settings (self-hosted only)
|
|
66
|
+
* description: Returns AI configuration. API key is masked. Cloud mode returns 403.
|
|
67
|
+
* tags: [Admin - Settings]
|
|
68
|
+
* security:
|
|
69
|
+
* - cookieAuth: []
|
|
70
|
+
* - bearerAuth: []
|
|
71
|
+
* responses:
|
|
72
|
+
* 200:
|
|
73
|
+
* description: AI settings
|
|
74
|
+
* 403:
|
|
75
|
+
* description: Cloud mode - AI configured via env
|
|
76
|
+
*/
|
|
77
|
+
router.get('/ai', async (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const tenantId = getTenantId(req);
|
|
80
|
+
const keys = ['ai_enabled', 'ai_provider', 'ai_api_key', 'ai_model'];
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const key of keys) {
|
|
83
|
+
const row = await queryOne('SELECT value FROM system_config WHERE key = ? AND tenant_id = ?', [key, tenantId]);
|
|
84
|
+
const val = row ? row.value : '';
|
|
85
|
+
result[key] = key === 'ai_api_key' && val ? '***SET***' : (val || '');
|
|
86
|
+
}
|
|
87
|
+
res.json({
|
|
88
|
+
ai_enabled: result.ai_enabled === 'true',
|
|
89
|
+
ai_provider: result.ai_provider || 'openai',
|
|
90
|
+
ai_model: result.ai_model || 'gpt-4o-mini',
|
|
91
|
+
ai_api_key_set: result.ai_api_key === '***SET***',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
res.status(500).json({ error: error.message });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
/**
|
|
99
|
+
* @swagger
|
|
100
|
+
* /api/admin/settings/ai/models:
|
|
101
|
+
* get:
|
|
102
|
+
* summary: List available AI models (self-hosted only)
|
|
103
|
+
* description: Returns models for the configured provider. Requires API key to be set and saved. Cloud mode returns 403.
|
|
104
|
+
* tags: [Admin - Settings]
|
|
105
|
+
* security:
|
|
106
|
+
* - cookieAuth: []
|
|
107
|
+
* - bearerAuth: []
|
|
108
|
+
* responses:
|
|
109
|
+
* 200:
|
|
110
|
+
* description: List of models (id only)
|
|
111
|
+
* 400:
|
|
112
|
+
* description: API key not set or invalid
|
|
113
|
+
* 403:
|
|
114
|
+
* description: Cloud mode
|
|
115
|
+
*/
|
|
116
|
+
router.get('/ai/models', async (req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
const tenantId = getTenantId(req);
|
|
119
|
+
const providerRow = await queryOne('SELECT value FROM system_config WHERE key = ? AND tenant_id = ?', ['ai_provider', tenantId]);
|
|
120
|
+
const keyRow = await queryOne('SELECT value FROM system_config WHERE key = ? AND tenant_id = ?', ['ai_api_key', tenantId]);
|
|
121
|
+
const provider = (providerRow && providerRow.value) ? String(providerRow.value) : 'openai';
|
|
122
|
+
const rawKey = (keyRow && keyRow.value) ? keyRow.value : '';
|
|
123
|
+
if (!rawKey || rawKey.trim() === '') {
|
|
124
|
+
return res.status(400).json({ error: 'API key required to list models. Set and save your API key first.' });
|
|
125
|
+
}
|
|
126
|
+
let apiKey;
|
|
127
|
+
try {
|
|
128
|
+
apiKey = decrypt(rawKey);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return res.status(400).json({ error: 'Could not read API key. Save it again and retry.' });
|
|
132
|
+
}
|
|
133
|
+
if (!apiKey || !apiKey.trim()) {
|
|
134
|
+
return res.status(400).json({ error: 'API key required to list models. Set and save your API key first.' });
|
|
135
|
+
}
|
|
136
|
+
if (provider === 'openai') {
|
|
137
|
+
const models = await listOpenAIModels(apiKey.trim());
|
|
138
|
+
return res.json({ models });
|
|
139
|
+
}
|
|
140
|
+
return res.json({ models: [] });
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const err = error;
|
|
144
|
+
res.status(500).json({ error: err?.message ?? 'Failed to list models' });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
61
147
|
/**
|
|
62
148
|
* @swagger
|
|
63
149
|
* /api/admin/settings/{key}:
|
|
@@ -363,43 +449,6 @@ router.post('/smtp', async (req, res) => {
|
|
|
363
449
|
res.status(500).json({ error: error.message });
|
|
364
450
|
}
|
|
365
451
|
});
|
|
366
|
-
/**
|
|
367
|
-
* @swagger
|
|
368
|
-
* /api/admin/settings/ai:
|
|
369
|
-
* get:
|
|
370
|
-
* summary: Get AI settings (self-hosted only)
|
|
371
|
-
* description: Returns AI configuration. API key is masked. Cloud mode returns 403.
|
|
372
|
-
* tags: [Admin - Settings]
|
|
373
|
-
* security:
|
|
374
|
-
* - cookieAuth: []
|
|
375
|
-
* - bearerAuth: []
|
|
376
|
-
* responses:
|
|
377
|
-
* 200:
|
|
378
|
-
* description: AI settings
|
|
379
|
-
* 403:
|
|
380
|
-
* description: Cloud mode - AI configured via env
|
|
381
|
-
*/
|
|
382
|
-
router.get('/ai', async (req, res) => {
|
|
383
|
-
try {
|
|
384
|
-
const tenantId = getTenantId(req);
|
|
385
|
-
const keys = ['ai_enabled', 'ai_provider', 'ai_api_key', 'ai_model'];
|
|
386
|
-
const result = {};
|
|
387
|
-
for (const key of keys) {
|
|
388
|
-
const row = await queryOne('SELECT value FROM system_config WHERE key = ? AND tenant_id = ?', [key, tenantId]);
|
|
389
|
-
const val = row ? row.value : '';
|
|
390
|
-
result[key] = key === 'ai_api_key' && val ? '***SET***' : (val || '');
|
|
391
|
-
}
|
|
392
|
-
res.json({
|
|
393
|
-
ai_enabled: result.ai_enabled === 'true',
|
|
394
|
-
ai_provider: result.ai_provider || 'openai',
|
|
395
|
-
ai_model: result.ai_model || 'gpt-4o-mini',
|
|
396
|
-
ai_api_key_set: result.ai_api_key === '***SET***',
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
catch (error) {
|
|
400
|
-
res.status(500).json({ error: error.message });
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
452
|
/**
|
|
404
453
|
* @swagger
|
|
405
454
|
* /api/admin/settings/ai:
|
|
@@ -426,55 +475,6 @@ router.get('/ai', async (req, res) => {
|
|
|
426
475
|
* 403:
|
|
427
476
|
* description: Cloud mode
|
|
428
477
|
*/
|
|
429
|
-
/**
|
|
430
|
-
* @swagger
|
|
431
|
-
* /api/admin/settings/ai/models:
|
|
432
|
-
* get:
|
|
433
|
-
* summary: List available AI models (self-hosted only)
|
|
434
|
-
* description: Returns models for the configured provider. Requires API key to be set and saved. Cloud mode returns 403.
|
|
435
|
-
* tags: [Admin - Settings]
|
|
436
|
-
* security:
|
|
437
|
-
* - cookieAuth: []
|
|
438
|
-
* - bearerAuth: []
|
|
439
|
-
* responses:
|
|
440
|
-
* 200:
|
|
441
|
-
* description: List of models (id only)
|
|
442
|
-
* 400:
|
|
443
|
-
* description: API key not set or invalid
|
|
444
|
-
* 403:
|
|
445
|
-
* description: Cloud mode
|
|
446
|
-
*/
|
|
447
|
-
router.get('/ai/models', async (req, res) => {
|
|
448
|
-
try {
|
|
449
|
-
const tenantId = getTenantId(req);
|
|
450
|
-
const providerRow = await queryOne('SELECT value FROM system_config WHERE key = ? AND tenant_id = ?', ['ai_provider', tenantId]);
|
|
451
|
-
const keyRow = await queryOne('SELECT value FROM system_config WHERE key = ? AND tenant_id = ?', ['ai_api_key', tenantId]);
|
|
452
|
-
const provider = (providerRow && providerRow.value) ? String(providerRow.value) : 'openai';
|
|
453
|
-
const rawKey = (keyRow && keyRow.value) ? keyRow.value : '';
|
|
454
|
-
if (!rawKey || rawKey.trim() === '') {
|
|
455
|
-
return res.status(400).json({ error: 'API key required to list models. Set and save your API key first.' });
|
|
456
|
-
}
|
|
457
|
-
let apiKey;
|
|
458
|
-
try {
|
|
459
|
-
apiKey = decrypt(rawKey);
|
|
460
|
-
}
|
|
461
|
-
catch {
|
|
462
|
-
return res.status(400).json({ error: 'Could not read API key. Save it again and retry.' });
|
|
463
|
-
}
|
|
464
|
-
if (!apiKey || !apiKey.trim()) {
|
|
465
|
-
return res.status(400).json({ error: 'API key required to list models. Set and save your API key first.' });
|
|
466
|
-
}
|
|
467
|
-
if (provider === 'openai') {
|
|
468
|
-
const models = await listOpenAIModels(apiKey.trim());
|
|
469
|
-
return res.json({ models });
|
|
470
|
-
}
|
|
471
|
-
return res.json({ models: [] });
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
const err = error;
|
|
475
|
-
res.status(500).json({ error: err?.message ?? 'Failed to list models' });
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
478
|
router.post('/ai', async (req, res) => {
|
|
479
479
|
try {
|
|
480
480
|
const tenantId = getTenantId(req);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/routes/admin/settings.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/routes/admin/settings.ts"],"names":[],"mappings":"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"}
|
package/frontend/src/App.tsx
CHANGED
|
@@ -30,25 +30,27 @@ const GoPreferences = lazy(() => import('./pages/GoPreferences'));
|
|
|
30
30
|
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
|
31
31
|
const { user, loading } = useAuth();
|
|
32
32
|
const { t } = useTranslation();
|
|
33
|
-
const {
|
|
33
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
34
|
+
const loginPath = `${pathPrefixForLinks || ''}/login`.replace(/\/+/g, '/') || '/login';
|
|
34
35
|
if (loading) return <div className="min-h-screen flex items-center justify-center"><div className="text-lg">{t('common.loading')}</div></div>;
|
|
35
|
-
if (!user) return <Navigate to={
|
|
36
|
+
if (!user) return <Navigate to={loginPath} replace />;
|
|
36
37
|
return <>{children}</>;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
function AdminRoute({ children }: { children: React.ReactNode }) {
|
|
40
41
|
const { user, loading } = useAuth();
|
|
41
42
|
const { t } = useTranslation();
|
|
42
|
-
const {
|
|
43
|
+
const { pathPrefixForLinks, appRootPath } = useAppConfig();
|
|
44
|
+
const loginPath = `${pathPrefixForLinks || ''}/login`.replace(/\/+/g, '/') || '/login';
|
|
43
45
|
if (loading) return <div className="min-h-screen flex items-center justify-center"><div className="text-lg">{t('common.loading')}</div></div>;
|
|
44
|
-
if (!user) return <Navigate to={
|
|
46
|
+
if (!user) return <Navigate to={loginPath} replace />;
|
|
45
47
|
if (!user.is_admin) return <Navigate to={appRootPath} replace />;
|
|
46
48
|
return <>{children}</>;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function SharedRedirect() {
|
|
50
|
-
const {
|
|
51
|
-
const to = `${
|
|
52
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
53
|
+
const to = `${pathPrefixForLinks || ''}/bookmarks?scope=shared_with_me`.replace(/\/+/g, '/') || '/bookmarks?scope=shared_with_me';
|
|
52
54
|
return <Navigate to={to} replace />;
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -74,7 +76,7 @@ class LoginRouteErrorBoundary extends Component<{ children: ReactNode }, { error
|
|
|
74
76
|
function AppRoutes() {
|
|
75
77
|
const { user, loading } = useAuth();
|
|
76
78
|
const { t } = useTranslation();
|
|
77
|
-
const { appRootPath, skipSetupFlow } = useAppConfig();
|
|
79
|
+
const { appRootPath, skipSetupFlow, hideAdminOidcAndSmtp } = useAppConfig();
|
|
78
80
|
const [setupStatus, setSetupStatus] = React.useState<{ initialized: boolean } | null>(() =>
|
|
79
81
|
skipSetupFlow ? { initialized: true } : null
|
|
80
82
|
);
|
|
@@ -120,8 +122,17 @@ function AppRoutes() {
|
|
|
120
122
|
<Route index element={<Navigate to="members" replace />} />
|
|
121
123
|
<Route path="members" element={<AdminMembersPage />} />
|
|
122
124
|
<Route path="teams" element={<AdminTeamsPage />} />
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
{hideAdminOidcAndSmtp ? (
|
|
126
|
+
<>
|
|
127
|
+
<Route path="oidc" element={<Navigate to="members" replace />} />
|
|
128
|
+
<Route path="settings" element={<Navigate to="members" replace />} />
|
|
129
|
+
</>
|
|
130
|
+
) : (
|
|
131
|
+
<>
|
|
132
|
+
<Route path="oidc" element={<AdminOIDCPage />} />
|
|
133
|
+
<Route path="settings" element={<AdminSettingsPage />} />
|
|
134
|
+
</>
|
|
135
|
+
)}
|
|
125
136
|
<Route path="ai" element={<AdminAIPage />} />
|
|
126
137
|
</Route>
|
|
127
138
|
</Route>
|
|
@@ -225,9 +236,11 @@ export interface AppProps {
|
|
|
225
236
|
routerBasename?: string | null;
|
|
226
237
|
/** When true, skip the first-time setup flow (e.g. in cloud; first user registers via Signup). */
|
|
227
238
|
skipSetupFlow?: boolean;
|
|
239
|
+
/** When true, hide Admin OIDC and SMTP/Settings (e.g. cloud uses Postmark and global OIDC). */
|
|
240
|
+
hideAdminOidcAndSmtp?: boolean;
|
|
228
241
|
}
|
|
229
242
|
|
|
230
|
-
function App({ basePath, apiBaseUrl, routerBasename, skipSetupFlow }: AppProps = {}) {
|
|
243
|
+
function App({ basePath, apiBaseUrl, routerBasename, skipSetupFlow, hideAdminOidcAndSmtp }: AppProps = {}) {
|
|
231
244
|
const appRootPath = basePath === '/' || !basePath ? '/' : basePath;
|
|
232
245
|
const pathPrefixForLinks = routerBasename !== undefined ? '' : (basePath ?? '');
|
|
233
246
|
const content = (
|
|
@@ -241,7 +254,7 @@ function App({ basePath, apiBaseUrl, routerBasename, skipSetupFlow }: AppProps =
|
|
|
241
254
|
);
|
|
242
255
|
return (
|
|
243
256
|
<AppErrorBoundary>
|
|
244
|
-
<AppConfigProvider appBasePath={basePath} apiBaseUrl={apiBaseUrl} appRootPath={appRootPath} skipSetupFlow={skipSetupFlow} pathPrefixForLinks={pathPrefixForLinks}>
|
|
257
|
+
<AppConfigProvider appBasePath={basePath} apiBaseUrl={apiBaseUrl} appRootPath={appRootPath} skipSetupFlow={skipSetupFlow} pathPrefixForLinks={pathPrefixForLinks} hideAdminOidcAndSmtp={hideAdminOidcAndSmtp}>
|
|
245
258
|
{routerBasename !== undefined ? (
|
|
246
259
|
content
|
|
247
260
|
) : (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
1
2
|
import { Link, useLocation } from 'react-router-dom';
|
|
2
3
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
Bookmark,
|
|
6
6
|
Folder,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from './ui/sidebar';
|
|
31
31
|
import { useAppConfig } from '../contexts/AppConfigContext';
|
|
32
32
|
import type { User } from '../contexts/AuthContext';
|
|
33
|
-
import { cn } from '
|
|
33
|
+
import { cn } from '../lib/utils';
|
|
34
34
|
|
|
35
35
|
const SIDEBAR_ADMIN_OPEN_KEY = 'slugbase_sidebar_admin_open';
|
|
36
36
|
|
|
@@ -43,16 +43,22 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
|
|
|
43
43
|
const { t } = useTranslation();
|
|
44
44
|
const location = useLocation();
|
|
45
45
|
const pathname = location.pathname;
|
|
46
|
-
const { appBasePath } = useAppConfig();
|
|
46
|
+
const { appBasePath, pathPrefixForLinks, hideAdminOidcAndSmtp } = useAppConfig();
|
|
47
47
|
const { setOpenMobile, toggleSidebar, isMobile, state } = useSidebar();
|
|
48
|
-
const
|
|
48
|
+
const prefix = pathPrefixForLinks || '';
|
|
49
|
+
const adminBaseFull = `${appBasePath || ''}/admin`;
|
|
50
|
+
const adminBaseLink = `${prefix}/admin`.replace(/\/+/g, '/') || '/admin';
|
|
49
51
|
|
|
50
52
|
const adminNavItems = [
|
|
51
|
-
{
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
{ pathForLink: `${adminBaseLink}/members`, pathForActive: `${adminBaseFull}/members`, label: t('admin.users'), icon: Users },
|
|
54
|
+
{ pathForLink: `${adminBaseLink}/teams`, pathForActive: `${adminBaseFull}/teams`, label: t('admin.teams'), icon: UserCog },
|
|
55
|
+
...(!hideAdminOidcAndSmtp
|
|
56
|
+
? [
|
|
57
|
+
{ pathForLink: `${adminBaseLink}/oidc`, pathForActive: `${adminBaseFull}/oidc`, label: t('admin.oidcProviders'), icon: Key },
|
|
58
|
+
{ pathForLink: `${adminBaseLink}/settings`, pathForActive: `${adminBaseFull}/settings`, label: t('admin.settings'), icon: Settings },
|
|
59
|
+
]
|
|
60
|
+
: []),
|
|
61
|
+
{ pathForLink: `${adminBaseLink}/ai`, pathForActive: `${adminBaseFull}/ai`, label: t('admin.ai.nav'), icon: Sparkles },
|
|
56
62
|
];
|
|
57
63
|
|
|
58
64
|
const isOverviewActive =
|
|
@@ -71,11 +77,13 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
|
|
|
71
77
|
localStorage.setItem(SIDEBAR_ADMIN_OPEN_KEY, String(adminOpen));
|
|
72
78
|
}, [adminOpen]);
|
|
73
79
|
|
|
80
|
+
const rootLink = prefix || '/';
|
|
81
|
+
const rootActive = appBasePath || '/';
|
|
74
82
|
const primaryNavItems = [
|
|
75
|
-
{
|
|
76
|
-
{
|
|
77
|
-
{
|
|
78
|
-
{
|
|
83
|
+
{ pathForLink: rootLink, pathForActive: rootActive, label: t('dashboard.overview'), icon: LayoutDashboard },
|
|
84
|
+
{ pathForLink: `${prefix}/bookmarks`.replace(/\/+/g, '/') || '/bookmarks', pathForActive: `${appBasePath || ''}/bookmarks`, label: t('bookmarks.title'), icon: Bookmark },
|
|
85
|
+
{ pathForLink: `${prefix}/folders`.replace(/\/+/g, '/') || '/folders', pathForActive: `${appBasePath || ''}/folders`, label: t('folders.title'), icon: Folder },
|
|
86
|
+
{ pathForLink: `${prefix}/tags`.replace(/\/+/g, '/') || '/tags', pathForActive: `${appBasePath || ''}/tags`, label: t('tags.title'), icon: Tag },
|
|
79
87
|
];
|
|
80
88
|
|
|
81
89
|
const handleNavClick = () => {
|
|
@@ -85,21 +93,22 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
|
|
|
85
93
|
};
|
|
86
94
|
|
|
87
95
|
return (
|
|
96
|
+
<React.Fragment>
|
|
88
97
|
<Sidebar collapsible="icon" side="left">
|
|
89
98
|
<SidebarContent>
|
|
90
99
|
<SidebarGroup>
|
|
91
100
|
<SidebarGroupContent>
|
|
92
101
|
<SidebarMenu>
|
|
93
102
|
{primaryNavItems.map((item) => (
|
|
94
|
-
<SidebarMenuItem key={item.
|
|
103
|
+
<SidebarMenuItem key={item.pathForLink}>
|
|
95
104
|
<SidebarMenuButton
|
|
96
105
|
asChild
|
|
97
106
|
isActive={
|
|
98
|
-
item.
|
|
107
|
+
item.pathForActive === (appBasePath || '/') ? isOverviewActive : pathname === item.pathForActive
|
|
99
108
|
}
|
|
100
109
|
tooltip={item.label}
|
|
101
110
|
>
|
|
102
|
-
<Link to={item.
|
|
111
|
+
<Link to={item.pathForLink} onClick={handleNavClick} aria-current={pathname === item.pathForActive ? 'page' : undefined}>
|
|
103
112
|
<item.icon className="h-5 w-5" />
|
|
104
113
|
<span>{item.label}</span>
|
|
105
114
|
</Link>
|
|
@@ -138,16 +147,16 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
|
|
|
138
147
|
{adminNavItems.map((item) => {
|
|
139
148
|
const Icon = item.icon;
|
|
140
149
|
return (
|
|
141
|
-
<SidebarMenuItem key={item.
|
|
150
|
+
<SidebarMenuItem key={item.pathForLink}>
|
|
142
151
|
<SidebarMenuButton
|
|
143
152
|
asChild
|
|
144
|
-
isActive={pathname === item.
|
|
153
|
+
isActive={pathname === item.pathForActive}
|
|
145
154
|
tooltip={item.label}
|
|
146
155
|
>
|
|
147
156
|
<Link
|
|
148
|
-
to={item.
|
|
157
|
+
to={item.pathForLink}
|
|
149
158
|
onClick={handleNavClick}
|
|
150
|
-
aria-current={pathname === item.
|
|
159
|
+
aria-current={pathname === item.pathForActive ? 'page' : undefined}
|
|
151
160
|
>
|
|
152
161
|
<Icon className="h-5 w-5" />
|
|
153
162
|
<span>{item.label}</span>
|
|
@@ -210,5 +219,6 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
|
|
|
210
219
|
</SidebarGroup>
|
|
211
220
|
</SidebarFooter>
|
|
212
221
|
</Sidebar>
|
|
222
|
+
</React.Fragment>
|
|
213
223
|
);
|
|
214
224
|
}
|
|
@@ -37,7 +37,8 @@ interface SearchResult {
|
|
|
37
37
|
export default function GlobalSearch() {
|
|
38
38
|
const { t } = useTranslation();
|
|
39
39
|
const navigate = useNavigate();
|
|
40
|
-
const {
|
|
40
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
41
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
41
42
|
const { user } = useAuth();
|
|
42
43
|
const { open, setOpen, openSearch } = useSearchCommand();
|
|
43
44
|
const [query, setQuery] = useState('');
|
|
@@ -47,19 +48,19 @@ export default function GlobalSearch() {
|
|
|
47
48
|
const showAdmin = user?.is_admin;
|
|
48
49
|
|
|
49
50
|
const navigationItems: SearchResult[] = useMemo(() => [
|
|
50
|
-
{ type: 'navigation', title: t('bookmarks.title'), path: `${
|
|
51
|
-
{ type: 'navigation', title: t('folders.title'), path: `${
|
|
52
|
-
{ type: 'navigation', title: t('tags.title'), path: `${
|
|
53
|
-
{ type: 'navigation', title: t('shared.title'), path: `${
|
|
54
|
-
...(showAdmin ? [{ type: 'navigation' as const, title: t('admin.title'), path: `${
|
|
55
|
-
], [showAdmin, t,
|
|
51
|
+
{ type: 'navigation', title: t('bookmarks.title'), path: `${prefix}/bookmarks`.replace(/\/+/g, '/') || '/bookmarks', id: 'nav-bookmarks' },
|
|
52
|
+
{ type: 'navigation', title: t('folders.title'), path: `${prefix}/folders`.replace(/\/+/g, '/') || '/folders', id: 'nav-folders' },
|
|
53
|
+
{ type: 'navigation', title: t('tags.title'), path: `${prefix}/tags`.replace(/\/+/g, '/') || '/tags', id: 'nav-tags' },
|
|
54
|
+
{ type: 'navigation', title: t('shared.title'), path: `${prefix}/shared`.replace(/\/+/g, '/') || '/shared', id: 'nav-shared' },
|
|
55
|
+
...(showAdmin ? [{ type: 'navigation' as const, title: t('admin.title'), path: `${prefix}/admin/members`.replace(/\/+/g, '/') || '/admin/members', id: 'nav-admin' }] : []),
|
|
56
|
+
], [showAdmin, t, prefix]);
|
|
56
57
|
|
|
57
58
|
const actionItems: SearchResult[] = useMemo(() => [
|
|
58
|
-
{ type: 'action', title: t('bookmarks.create'), path: `${
|
|
59
|
-
{ type: 'action', title: t('folders.create'), path: `${
|
|
60
|
-
{ type: 'action', title: t('bookmarks.import'), path: `${
|
|
61
|
-
{ type: 'action', title: t('bookmarks.export'), path: `${
|
|
62
|
-
], [t, navigate,
|
|
59
|
+
{ type: 'action', title: t('bookmarks.create'), path: `${prefix}/bookmarks`, id: 'action-create-bookmark', action: () => navigate(`${prefix}/bookmarks?create=true`.replace(/\/+/g, '/') || '/bookmarks?create=true') },
|
|
60
|
+
{ type: 'action', title: t('folders.create'), path: `${prefix}/folders`, id: 'action-create-folder', action: () => navigate(`${prefix}/folders?create=true`.replace(/\/+/g, '/') || '/folders?create=true') },
|
|
61
|
+
{ type: 'action', title: t('bookmarks.import'), path: `${prefix}/bookmarks`, id: 'action-import', action: () => navigate(`${prefix}/bookmarks?import=true`.replace(/\/+/g, '/') || '/bookmarks?import=true') },
|
|
62
|
+
{ type: 'action', title: t('bookmarks.export'), path: `${prefix}/bookmarks`, id: 'action-export', action: () => navigate(`${prefix}/bookmarks?export=true`.replace(/\/+/g, '/') || '/bookmarks?export=true') },
|
|
63
|
+
], [t, navigate, prefix]);
|
|
63
64
|
|
|
64
65
|
useEffect(() => {
|
|
65
66
|
function handleKeyDown(e: KeyboardEvent) {
|
|
@@ -173,9 +174,9 @@ export default function GlobalSearch() {
|
|
|
173
174
|
api.post(`/bookmarks/${result.id}/track-access`).catch(() => {});
|
|
174
175
|
window.open(result.url, '_blank', 'noopener,noreferrer');
|
|
175
176
|
} else if (result.type === 'folder') {
|
|
176
|
-
navigate(`${
|
|
177
|
+
navigate(`${prefix}/bookmarks?folder_id=${result.id}`.replace(/\/+/g, '/') || `/bookmarks?folder_id=${result.id}`);
|
|
177
178
|
} else if (result.type === 'tag') {
|
|
178
|
-
navigate(`${
|
|
179
|
+
navigate(`${prefix}/bookmarks?tag_id=${result.id}`.replace(/\/+/g, '/') || `/bookmarks?tag_id=${result.id}`);
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -14,7 +14,8 @@ interface TopBarProps {
|
|
|
14
14
|
|
|
15
15
|
export default function TopBar({ user }: TopBarProps) {
|
|
16
16
|
const { t } = useTranslation();
|
|
17
|
-
const {
|
|
17
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
18
|
+
const prefix = pathPrefixForLinks || '';
|
|
18
19
|
const { isMobile } = useSidebar();
|
|
19
20
|
|
|
20
21
|
return (
|
|
@@ -25,7 +26,7 @@ export default function TopBar({ user }: TopBarProps) {
|
|
|
25
26
|
<SidebarTrigger className="-ml-2" aria-label={t('common.expandSidebar')} />
|
|
26
27
|
)}
|
|
27
28
|
<Link
|
|
28
|
-
to={
|
|
29
|
+
to={prefix || '/'}
|
|
29
30
|
className="flex items-center gap-2 text-xl font-bold text-foreground hover:text-primary transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-lg"
|
|
30
31
|
>
|
|
31
32
|
<img
|
|
@@ -51,7 +52,7 @@ export default function TopBar({ user }: TopBarProps) {
|
|
|
51
52
|
|
|
52
53
|
{/* Right: Create bookmark + Profile — right-aligned */}
|
|
53
54
|
<div className="flex items-center gap-2 sm:gap-4 shrink-0 ml-auto">
|
|
54
|
-
<Link to={`${
|
|
55
|
+
<Link to={`${prefix}/bookmarks?create=true`.replace(/\/+/g, '/') || '/bookmarks?create=true'}>
|
|
55
56
|
<Button variant="primary" size="sm" icon={Plus}>
|
|
56
57
|
<span className="hidden sm:inline">{t('bookmarks.create')}</span>
|
|
57
58
|
</Button>
|
|
@@ -27,7 +27,8 @@ function getInitials(name: string): string {
|
|
|
27
27
|
|
|
28
28
|
export default function UserDropdown({ user }: UserDropdownProps) {
|
|
29
29
|
const { t } = useTranslation();
|
|
30
|
-
const {
|
|
30
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
31
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
31
32
|
const { logout } = useAuth();
|
|
32
33
|
|
|
33
34
|
const showAdmin = user?.is_admin;
|
|
@@ -59,14 +60,14 @@ export default function UserDropdown({ user }: UserDropdownProps) {
|
|
|
59
60
|
</DropdownMenuLabel>
|
|
60
61
|
<DropdownMenuSeparator />
|
|
61
62
|
<DropdownMenuItem asChild>
|
|
62
|
-
<Link to={`${
|
|
63
|
+
<Link to={`${prefix}/profile`} className="flex items-center gap-2 cursor-pointer">
|
|
63
64
|
<UserIcon className="h-4 w-4" />
|
|
64
65
|
{t('profile.title')}
|
|
65
66
|
</Link>
|
|
66
67
|
</DropdownMenuItem>
|
|
67
68
|
{showAdmin && (
|
|
68
69
|
<DropdownMenuItem asChild>
|
|
69
|
-
<Link to={`${
|
|
70
|
+
<Link to={`${prefix}/admin/members`} className="flex items-center gap-2 cursor-pointer">
|
|
70
71
|
<Settings className="h-4 w-4" />
|
|
71
72
|
{t('admin.title')}
|
|
72
73
|
</Link>
|
|
@@ -45,6 +45,18 @@ export default function AdminAI() {
|
|
|
45
45
|
setLoading(false);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
+
// 404 = no AI settings yet (first visit); use defaults without toasting
|
|
49
|
+
if (e?.response?.status === 404) {
|
|
50
|
+
setSettings({
|
|
51
|
+
ai_enabled: false,
|
|
52
|
+
ai_provider: 'openai',
|
|
53
|
+
ai_model: 'gpt-4o-mini',
|
|
54
|
+
ai_api_key: '',
|
|
55
|
+
ai_api_key_set: false,
|
|
56
|
+
});
|
|
57
|
+
setLoading(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
48
60
|
showToast(e?.response?.data?.error || t('common.error'), 'error');
|
|
49
61
|
} finally {
|
|
50
62
|
setLoading(false);
|
|
@@ -9,6 +9,8 @@ export interface AppConfig {
|
|
|
9
9
|
skipSetupFlow?: boolean;
|
|
10
10
|
/** Prefix for Link/Navigate paths. Empty when under external Router (e.g. cloud basename="/app"); otherwise appBasePath. Use for all in-app navigation. */
|
|
11
11
|
pathPrefixForLinks: string;
|
|
12
|
+
/** When true, hide Admin OIDC and SMTP/Settings (e.g. cloud uses Postmark and global OIDC). */
|
|
13
|
+
hideAdminOidcAndSmtp?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const defaultConfig: AppConfig = {
|
|
@@ -16,6 +18,7 @@ const defaultConfig: AppConfig = {
|
|
|
16
18
|
apiBaseUrl: defaultApiBaseUrl,
|
|
17
19
|
appRootPath: defaultAppRootPath,
|
|
18
20
|
pathPrefixForLinks: defaultAppBasePath,
|
|
21
|
+
hideAdminOidcAndSmtp: false,
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
const AppConfigContext = createContext<AppConfig>(defaultConfig);
|
|
@@ -27,6 +30,7 @@ export function AppConfigProvider({
|
|
|
27
30
|
appRootPath,
|
|
28
31
|
skipSetupFlow,
|
|
29
32
|
pathPrefixForLinks,
|
|
33
|
+
hideAdminOidcAndSmtp,
|
|
30
34
|
}: {
|
|
31
35
|
children: React.ReactNode;
|
|
32
36
|
appBasePath?: string;
|
|
@@ -35,6 +39,8 @@ export function AppConfigProvider({
|
|
|
35
39
|
skipSetupFlow?: boolean;
|
|
36
40
|
/** When set (e.g. "" when under external Router), used for Link/Navigate. Omit to use appBasePath. */
|
|
37
41
|
pathPrefixForLinks?: string;
|
|
42
|
+
/** When true, hide Admin OIDC and SMTP/Settings (e.g. cloud). */
|
|
43
|
+
hideAdminOidcAndSmtp?: boolean;
|
|
38
44
|
}) {
|
|
39
45
|
const base = appBasePath ?? defaultConfig.appBasePath;
|
|
40
46
|
const value: AppConfig = {
|
|
@@ -43,6 +49,7 @@ export function AppConfigProvider({
|
|
|
43
49
|
appRootPath: appRootPath ?? defaultConfig.appRootPath,
|
|
44
50
|
skipSetupFlow,
|
|
45
51
|
pathPrefixForLinks: pathPrefixForLinks !== undefined ? pathPrefixForLinks : base,
|
|
52
|
+
hideAdminOidcAndSmtp: hideAdminOidcAndSmtp ?? defaultConfig.hideAdminOidcAndSmtp,
|
|
46
53
|
};
|
|
47
54
|
return <AppConfigContext.Provider value={value}>{children}</AppConfigContext.Provider>;
|
|
48
55
|
}
|
|
@@ -47,7 +47,8 @@ type SortOption = 'recently_added' | 'alphabetical' | 'most_used' | 'recently_ac
|
|
|
47
47
|
|
|
48
48
|
export default function Bookmarks() {
|
|
49
49
|
const { t } = useTranslation();
|
|
50
|
-
const {
|
|
50
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
51
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
51
52
|
const { user } = useAuth();
|
|
52
53
|
const { isMobile, state: sidebarState } = useSidebar();
|
|
53
54
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
@@ -823,7 +824,7 @@ export default function Bookmarks() {
|
|
|
823
824
|
>
|
|
824
825
|
{t('bookmarks.emptyImport')}
|
|
825
826
|
</Button>
|
|
826
|
-
<Link to={`${
|
|
827
|
+
<Link to={`${prefix}/search-engine-guide`}>
|
|
827
828
|
<Button variant="ghost" icon={ExternalLink}>
|
|
828
829
|
{t('bookmarks.emptyLearnForwarding')}
|
|
829
830
|
</Button>
|
|
@@ -921,7 +922,7 @@ export default function Bookmarks() {
|
|
|
921
922
|
<p className="text-sm text-gray-700 dark:text-gray-300">
|
|
922
923
|
{t('bookmarks.searchEngineNote')}{' '}
|
|
923
924
|
<Link
|
|
924
|
-
to={`${
|
|
925
|
+
to={`${prefix}/search-engine-guide`}
|
|
925
926
|
className="text-primary hover:text-primary/90 font-medium underline"
|
|
926
927
|
>
|
|
927
928
|
{t('bookmarks.searchEngineGuideLink')}
|
|
@@ -61,18 +61,18 @@ const ONBOARDING_DISMISSED_KEY = 'slugbase_dashboard_onboarding_dismissed';
|
|
|
61
61
|
|
|
62
62
|
function ProTipBanner({
|
|
63
63
|
onDismiss,
|
|
64
|
-
|
|
64
|
+
pathPrefix,
|
|
65
65
|
t,
|
|
66
66
|
}: {
|
|
67
67
|
onDismiss: () => void;
|
|
68
|
-
|
|
68
|
+
pathPrefix: string;
|
|
69
69
|
t: (key: string) => string;
|
|
70
70
|
}) {
|
|
71
71
|
return (
|
|
72
72
|
<div className="flex items-start gap-3 rounded-xl border border-border bg-card shadow-sm px-4 py-3">
|
|
73
73
|
<p className="text-sm text-muted-foreground flex-1 min-w-0">
|
|
74
74
|
{t('dashboard.proTipBody')}{' '}
|
|
75
|
-
<Link to={`${
|
|
75
|
+
<Link to={`${pathPrefix}/search-engine-guide`.replace(/\/+/g, '/') || '/search-engine-guide'} className="text-primary font-medium hover:underline">
|
|
76
76
|
{t('dashboard.proTipLink')}
|
|
77
77
|
</Link>
|
|
78
78
|
</p>
|
|
@@ -92,13 +92,13 @@ function OnboardingChecklist({
|
|
|
92
92
|
totalBookmarks,
|
|
93
93
|
totalFolders,
|
|
94
94
|
topTagsCount,
|
|
95
|
-
|
|
95
|
+
pathPrefix,
|
|
96
96
|
t,
|
|
97
97
|
}: {
|
|
98
98
|
totalBookmarks: number;
|
|
99
99
|
totalFolders: number;
|
|
100
100
|
topTagsCount: number;
|
|
101
|
-
|
|
101
|
+
pathPrefix: string;
|
|
102
102
|
t: (key: string) => string;
|
|
103
103
|
}) {
|
|
104
104
|
const [collapsed, setCollapsed] = useState(true);
|
|
@@ -109,10 +109,10 @@ function OnboardingChecklist({
|
|
|
109
109
|
if (!show) return null;
|
|
110
110
|
|
|
111
111
|
const steps = [
|
|
112
|
-
{ done: totalBookmarks > 0, label: t('dashboard.onboardingImport'), to: `${
|
|
113
|
-
{ done: false, label: t('dashboard.onboardingSearchEngine'), to: `${
|
|
114
|
-
{ done: totalFolders > 0, label: t('dashboard.onboardingFolder'), to: `${
|
|
115
|
-
{ done: topTagsCount > 0, label: t('dashboard.onboardingTag'), to: `${
|
|
112
|
+
{ done: totalBookmarks > 0, label: t('dashboard.onboardingImport'), to: `${pathPrefix}/bookmarks?import=true`.replace(/\/+/g, '/') || '/bookmarks?import=true' },
|
|
113
|
+
{ done: false, label: t('dashboard.onboardingSearchEngine'), to: `${pathPrefix}/search-engine-guide`.replace(/\/+/g, '/') || '/search-engine-guide' },
|
|
114
|
+
{ done: totalFolders > 0, label: t('dashboard.onboardingFolder'), to: `${pathPrefix}/folders`.replace(/\/+/g, '/') || '/folders' },
|
|
115
|
+
{ done: topTagsCount > 0, label: t('dashboard.onboardingTag'), to: `${pathPrefix}/bookmarks`.replace(/\/+/g, '/') || '/bookmarks' },
|
|
116
116
|
];
|
|
117
117
|
|
|
118
118
|
function handleDismiss() {
|
|
@@ -161,7 +161,8 @@ function OnboardingChecklist({
|
|
|
161
161
|
|
|
162
162
|
export default function Dashboard() {
|
|
163
163
|
const { t } = useTranslation();
|
|
164
|
-
const {
|
|
164
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
165
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
165
166
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
|
166
167
|
const [proTipDismissed, setProTipDismissed] = useState(() => typeof window !== 'undefined' && !!localStorage.getItem(PRO_TIP_DISMISSED_KEY));
|
|
167
168
|
|
|
@@ -190,7 +191,7 @@ export default function Dashboard() {
|
|
|
190
191
|
localStorage.setItem(PRO_TIP_DISMISSED_KEY, '1');
|
|
191
192
|
setProTipDismissed(true);
|
|
192
193
|
}}
|
|
193
|
-
|
|
194
|
+
pathPrefix={prefix}
|
|
194
195
|
t={t}
|
|
195
196
|
/>
|
|
196
197
|
)}
|
|
@@ -202,7 +203,7 @@ export default function Dashboard() {
|
|
|
202
203
|
label={t('dashboard.statsBookmarks')}
|
|
203
204
|
value={stats.totalBookmarks}
|
|
204
205
|
icon={Bookmark}
|
|
205
|
-
href={
|
|
206
|
+
href={prefix + '/bookmarks'}
|
|
206
207
|
dense
|
|
207
208
|
iconContainerClassName="bg-primary/20"
|
|
208
209
|
iconColorClassName="text-primary"
|
|
@@ -211,7 +212,7 @@ export default function Dashboard() {
|
|
|
211
212
|
label={t('dashboard.statsFolders')}
|
|
212
213
|
value={stats.totalFolders}
|
|
213
214
|
icon={Folder}
|
|
214
|
-
href={
|
|
215
|
+
href={prefix + '/folders'}
|
|
215
216
|
dense
|
|
216
217
|
iconContainerClassName="bg-primary/20"
|
|
217
218
|
iconColorClassName="text-primary"
|
|
@@ -220,7 +221,7 @@ export default function Dashboard() {
|
|
|
220
221
|
label={t('dashboard.statsTags')}
|
|
221
222
|
value={stats.totalTags}
|
|
222
223
|
icon={Tag}
|
|
223
|
-
href={
|
|
224
|
+
href={prefix + '/tags'}
|
|
224
225
|
dense
|
|
225
226
|
iconContainerClassName="bg-primary/20"
|
|
226
227
|
iconColorClassName="text-primary"
|
|
@@ -235,7 +236,7 @@ export default function Dashboard() {
|
|
|
235
236
|
{t('dashboard.pinned')}
|
|
236
237
|
</h2>
|
|
237
238
|
<Link
|
|
238
|
-
to={
|
|
239
|
+
to={prefix + '/bookmarks?pinned=true'}
|
|
239
240
|
className="text-sm font-medium text-primary hover:underline"
|
|
240
241
|
>
|
|
241
242
|
{t('dashboard.viewAll')}
|
|
@@ -284,7 +285,7 @@ export default function Dashboard() {
|
|
|
284
285
|
title={t('dashboard.noPinnedBookmarks')}
|
|
285
286
|
description={t('dashboard.pinFromBookmarks')}
|
|
286
287
|
action={
|
|
287
|
-
<Link to={
|
|
288
|
+
<Link to={prefix + '/bookmarks'}>
|
|
288
289
|
<Button variant="secondary">{t('dashboard.pinFromBookmarksLink')}</Button>
|
|
289
290
|
</Link>
|
|
290
291
|
}
|
|
@@ -301,7 +302,7 @@ export default function Dashboard() {
|
|
|
301
302
|
{t('dashboard.quickAccess')}
|
|
302
303
|
</h2>
|
|
303
304
|
<Link
|
|
304
|
-
to={
|
|
305
|
+
to={prefix + '/bookmarks'}
|
|
305
306
|
className="text-sm font-medium text-primary hover:underline"
|
|
306
307
|
>
|
|
307
308
|
{t('dashboard.viewAll')}
|
|
@@ -350,7 +351,7 @@ export default function Dashboard() {
|
|
|
350
351
|
title={t('dashboard.noQuickAccessBookmarks')}
|
|
351
352
|
description={t('dashboard.noQuickAccessBookmarksHint')}
|
|
352
353
|
action={
|
|
353
|
-
<Link to={`${
|
|
354
|
+
<Link to={`${prefix}/bookmarks?create=true`}>
|
|
354
355
|
<Button variant="primary" icon={Plus}>{t('bookmarks.create')}</Button>
|
|
355
356
|
</Link>
|
|
356
357
|
}
|
|
@@ -371,7 +372,7 @@ export default function Dashboard() {
|
|
|
371
372
|
label={t('dashboard.sharedBookmarks')}
|
|
372
373
|
value={stats.sharedBookmarks}
|
|
373
374
|
icon={Share2}
|
|
374
|
-
href={
|
|
375
|
+
href={prefix + '/shared'}
|
|
375
376
|
iconContainerClassName="bg-primary/20"
|
|
376
377
|
iconColorClassName="text-primary"
|
|
377
378
|
/>
|
|
@@ -379,7 +380,7 @@ export default function Dashboard() {
|
|
|
379
380
|
label={t('dashboard.sharedFolders')}
|
|
380
381
|
value={stats.sharedFolders}
|
|
381
382
|
icon={Share2}
|
|
382
|
-
href={
|
|
383
|
+
href={prefix + '/shared'}
|
|
383
384
|
iconContainerClassName="bg-primary/20"
|
|
384
385
|
iconColorClassName="text-primary"
|
|
385
386
|
/>
|
|
@@ -398,7 +399,7 @@ export default function Dashboard() {
|
|
|
398
399
|
{stats.topTags.map((tag) => (
|
|
399
400
|
<Link
|
|
400
401
|
key={tag.id}
|
|
401
|
-
to={`${
|
|
402
|
+
to={`${prefix}/bookmarks?tag_id=${tag.id}`}
|
|
402
403
|
className="inline-flex items-center gap-1.5 rounded-md border border-border bg-card px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-accent hover:border-primary/50 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
403
404
|
title={t('dashboard.filterByTagHint')}
|
|
404
405
|
>
|
|
@@ -418,7 +419,7 @@ export default function Dashboard() {
|
|
|
418
419
|
totalBookmarks={stats.totalBookmarks}
|
|
419
420
|
totalFolders={stats.totalFolders}
|
|
420
421
|
topTagsCount={stats.topTags.length}
|
|
421
|
-
|
|
422
|
+
pathPrefix={prefix}
|
|
422
423
|
t={t}
|
|
423
424
|
/>
|
|
424
425
|
)}
|
|
@@ -35,7 +35,8 @@ const DEFAULT_SORT: SortOption = 'alphabetical';
|
|
|
35
35
|
|
|
36
36
|
export default function Folders() {
|
|
37
37
|
const { t } = useTranslation();
|
|
38
|
-
const {
|
|
38
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
39
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
39
40
|
const { showConfirm, dialogState } = useConfirmDialog();
|
|
40
41
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
41
42
|
const [folders, setFolders] = useState<Folder[]>([]);
|
|
@@ -331,7 +332,7 @@ export default function Folders() {
|
|
|
331
332
|
className={`group bg-card rounded-lg border border-border hover:border-primary/70 hover:bg-muted/50 hover:shadow-md transition-all duration-200 flex flex-col h-full min-h-0 ${compactMode ? 'p-2.5 min-h-[160px]' : 'p-2.5 min-h-[140px]'}`}
|
|
332
333
|
>
|
|
333
334
|
<Link
|
|
334
|
-
to={`${
|
|
335
|
+
to={`${prefix}/bookmarks?folder_id=${folder.id}`}
|
|
335
336
|
className="flex-1 flex flex-col min-w-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
|
|
336
337
|
>
|
|
337
338
|
<div className="space-y-3 flex-1 flex flex-col">
|
|
@@ -437,7 +438,7 @@ export default function Folders() {
|
|
|
437
438
|
>
|
|
438
439
|
<td className={`${compactMode ? 'px-2 py-1.5' : 'px-4 py-3'}`}>
|
|
439
440
|
<Link
|
|
440
|
-
to={`${
|
|
441
|
+
to={`${prefix}/bookmarks?folder_id=${folder.id}`}
|
|
441
442
|
className={`flex items-center ${compactMode ? 'gap-2' : 'gap-3'} hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded`}
|
|
442
443
|
>
|
|
443
444
|
<div className={`flex-shrink-0 ${compactMode ? 'w-6 h-6' : 'w-8 h-8'} rounded-lg bg-primary/20 flex items-center justify-center border border-primary/30`}>
|
|
@@ -21,7 +21,8 @@ interface SlugPreference {
|
|
|
21
21
|
|
|
22
22
|
export default function GoPreferences() {
|
|
23
23
|
const { t } = useTranslation();
|
|
24
|
-
const {
|
|
24
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
25
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
25
26
|
const { showToast } = useToast();
|
|
26
27
|
const [preferences, setPreferences] = useState<SlugPreference[]>([]);
|
|
27
28
|
const [loading, setLoading] = useState(true);
|
|
@@ -64,7 +65,7 @@ export default function GoPreferences() {
|
|
|
64
65
|
return (
|
|
65
66
|
<div className="space-y-6 max-w-3xl">
|
|
66
67
|
<div className="flex items-center gap-4">
|
|
67
|
-
<Link to={`${
|
|
68
|
+
<Link to={`${prefix}/profile`}>
|
|
68
69
|
<Button variant="ghost" size="sm" icon={ArrowLeft}>
|
|
69
70
|
{t('common.back')}
|
|
70
71
|
</Button>
|
|
@@ -8,7 +8,8 @@ import Button from '../components/ui/Button';
|
|
|
8
8
|
|
|
9
9
|
export default function PasswordReset() {
|
|
10
10
|
const { t } = useTranslation();
|
|
11
|
-
const {
|
|
11
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
12
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
12
13
|
const [searchParams] = useSearchParams();
|
|
13
14
|
const navigate = useNavigate();
|
|
14
15
|
const token = searchParams.get('token');
|
|
@@ -143,7 +144,7 @@ export default function PasswordReset() {
|
|
|
143
144
|
|
|
144
145
|
<div className="text-center">
|
|
145
146
|
<Link
|
|
146
|
-
to={`${
|
|
147
|
+
to={`${prefix}/login`}
|
|
147
148
|
className="inline-flex items-center gap-2 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"
|
|
148
149
|
>
|
|
149
150
|
<ArrowLeft className="h-4 w-4" />
|
|
@@ -163,7 +164,7 @@ export default function PasswordReset() {
|
|
|
163
164
|
{t('passwordReset.invalidToken')}
|
|
164
165
|
</p>
|
|
165
166
|
<Link
|
|
166
|
-
to={`${
|
|
167
|
+
to={`${prefix}/password-reset`}
|
|
167
168
|
className="inline-flex items-center gap-2 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"
|
|
168
169
|
>
|
|
169
170
|
<ArrowLeft className="h-4 w-4" />
|
|
@@ -225,7 +226,7 @@ export default function PasswordReset() {
|
|
|
225
226
|
|
|
226
227
|
<div className="text-center">
|
|
227
228
|
<Link
|
|
228
|
-
to={`${
|
|
229
|
+
to={`${prefix}/login`}
|
|
229
230
|
className="inline-flex items-center gap-2 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"
|
|
230
231
|
>
|
|
231
232
|
<ArrowLeft className="h-4 w-4" />
|
|
@@ -58,7 +58,8 @@ function SettingsRow({
|
|
|
58
58
|
|
|
59
59
|
export default function Profile() {
|
|
60
60
|
const { t } = useTranslation();
|
|
61
|
-
const {
|
|
61
|
+
const { pathPrefixForLinks, apiBaseUrl } = useAppConfig();
|
|
62
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
62
63
|
const { user, updateUser, checkAuth } = useAuth();
|
|
63
64
|
const { showToast } = useToast();
|
|
64
65
|
const [formData, setFormData] = useState({
|
|
@@ -406,7 +407,7 @@ export default function Profile() {
|
|
|
406
407
|
<>
|
|
407
408
|
{t('profile.quickAccessDescription')}{' '}
|
|
408
409
|
<Link
|
|
409
|
-
to={`${
|
|
410
|
+
to={`${prefix}/go-preferences`}
|
|
410
411
|
className="text-primary hover:text-primary/90 font-medium"
|
|
411
412
|
>
|
|
412
413
|
{t('profile.manageQuickAccess')} →
|
|
@@ -6,7 +6,8 @@ import { useAppConfig } from '../contexts/AppConfigContext';
|
|
|
6
6
|
|
|
7
7
|
export default function SearchEngineGuide() {
|
|
8
8
|
const { t } = useTranslation();
|
|
9
|
-
const {
|
|
9
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
10
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
10
11
|
|
|
11
12
|
const baseUrl = window.location.origin;
|
|
12
13
|
const goPath = '/go/%s';
|
|
@@ -16,7 +17,7 @@ export default function SearchEngineGuide() {
|
|
|
16
17
|
<div className="space-y-6 max-w-4xl mx-auto">
|
|
17
18
|
{/* Header */}
|
|
18
19
|
<div className="flex items-center gap-4">
|
|
19
|
-
<Link to={`${
|
|
20
|
+
<Link to={`${prefix}/bookmarks`}>
|
|
20
21
|
<Button variant="ghost" size="sm" icon={ArrowLeft}>
|
|
21
22
|
{t('common.back')}
|
|
22
23
|
</Button>
|
|
@@ -27,7 +27,8 @@ const DEFAULT_SORT: SortOption = 'alphabetical';
|
|
|
27
27
|
|
|
28
28
|
export default function Tags() {
|
|
29
29
|
const { t } = useTranslation();
|
|
30
|
-
const {
|
|
30
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
31
|
+
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
31
32
|
const { showConfirm, dialogState } = useConfirmDialog();
|
|
32
33
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
33
34
|
const [tags, setTags] = useState<Tag[]>([]);
|
|
@@ -272,7 +273,7 @@ export default function Tags() {
|
|
|
272
273
|
className={`group bg-card rounded-lg border border-border hover:border-primary/70 hover:bg-muted/50 hover:shadow-md transition-all duration-200 flex flex-col h-full min-h-0 ${compactMode ? 'p-2.5 min-h-[160px]' : 'p-2.5 min-h-[140px]'}`}
|
|
273
274
|
>
|
|
274
275
|
<Link
|
|
275
|
-
to={`${
|
|
276
|
+
to={`${prefix}/bookmarks?tag_id=${tag.id}`}
|
|
276
277
|
className="flex-1 flex flex-col min-w-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
|
|
277
278
|
>
|
|
278
279
|
<div className="space-y-3 flex-1 flex flex-col">
|
|
@@ -335,7 +336,7 @@ export default function Tags() {
|
|
|
335
336
|
>
|
|
336
337
|
<td className={`${compactMode ? 'px-2 py-1.5' : 'px-4 py-3'}`}>
|
|
337
338
|
<Link
|
|
338
|
-
to={`${
|
|
339
|
+
to={`${prefix}/bookmarks?tag_id=${tag.id}`}
|
|
339
340
|
className={`flex items-center ${compactMode ? 'gap-2' : 'gap-3'} hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded`}
|
|
340
341
|
>
|
|
341
342
|
<div className={`flex-shrink-0 ${compactMode ? 'w-6 h-6' : 'w-8 h-8'} rounded-lg bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/30 dark:to-purple-800/20 flex items-center justify-center border border-purple-100 dark:border-purple-800/50`}>
|