@mdguggenbichler/slugbase-core 0.0.42 → 0.0.44

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.
@@ -3,7 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-route
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { AuthProvider, useAuth } from './contexts/AuthContext';
5
5
  import { AppConfigProvider, useAppConfig } from './contexts/AppConfigContext';
6
- import { PlanProvider } from './contexts/PlanContext';
6
+ import { PlanProvider, usePlan } from './contexts/PlanContext';
7
7
  import { ToastProvider } from './components/ui/Toast';
8
8
  import { TooltipProvider } from './components/ui/tooltip-base';
9
9
  import Layout from './components/Layout';
@@ -50,6 +50,17 @@ function AdminRoute({ children }: { children: React.ReactNode }) {
50
50
  return <>{children}</>;
51
51
  }
52
52
 
53
+ /** Redirect /admin to first visible admin tab (e.g. in cloud free plan, Users/Teams are hidden so redirect to ai or billing). */
54
+ function AdminIndexRedirect() {
55
+ const planInfo = usePlan();
56
+ const { extraAdminNavItems } = useAppConfig();
57
+ const showUsersAndTeams = !planInfo || planInfo.canShareWithTeams;
58
+ const firstPath = showUsersAndTeams
59
+ ? 'members'
60
+ : (extraAdminNavItems?.[0]?.path ?? 'ai');
61
+ return <Navigate to={firstPath} replace />;
62
+ }
63
+
53
64
  function SharedRedirect() {
54
65
  const { pathPrefixForLinks } = useAppConfig();
55
66
  const to = `${pathPrefixForLinks || ''}/bookmarks?scope=shared_with_me`.replace(/\/+/g, '/') || '/bookmarks?scope=shared_with_me';
@@ -122,7 +133,7 @@ function AppRoutes() {
122
133
  <Route path="go-preferences" element={<GoPreferences />} />
123
134
  <Route path="search-engine-guide" element={<SearchEngineGuide />} />
124
135
  <Route path="admin" element={<AdminRoute><AdminLayout /></AdminRoute>}>
125
- <Route index element={<Navigate to="members" replace />} />
136
+ <Route index element={<AdminIndexRedirect />} />
126
137
  <Route path="members" element={<AdminMembersPage />} />
127
138
  <Route path="teams" element={<AdminTeamsPage />} />
128
139
  {hideAdminOidcAndSmtp ? (
@@ -30,6 +30,7 @@ import {
30
30
  useSidebar,
31
31
  } from './ui/sidebar';
32
32
  import { useAppConfig } from '../contexts/AppConfigContext';
33
+ import { usePlan } from '../contexts/PlanContext';
33
34
  import type { User } from '../contexts/AuthContext';
34
35
  import { cn } from '../lib/utils';
35
36
 
@@ -45,6 +46,7 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
45
46
  const location = useLocation();
46
47
  const pathname = location.pathname;
47
48
  const { appBasePath, pathPrefixForLinks, hideAdminOidcAndSmtp, extraAdminNavItems } = useAppConfig();
49
+ const planInfo = usePlan();
48
50
  const { setOpenMobile, toggleSidebar, isMobile, state } = useSidebar();
49
51
  const prefix = pathPrefixForLinks || '';
50
52
  // For active matching use pathPrefixForLinks so it matches useLocation().pathname (e.g. when Router has basename="/app", pathname is "/bookmarks" not "/app/bookmarks").
@@ -52,9 +54,16 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
52
54
  const adminBaseFull = `${pathBaseForActive}/admin`.replace(/\/+/g, '/') || '/admin';
53
55
  const adminBaseLink = `${prefix}/admin`.replace(/\/+/g, '/') || '/admin';
54
56
 
57
+ // In cloud, show Users and Teams only on team plan; self-hosted (planInfo null) always shows them.
58
+ const showAdminUsersAndTeams = !planInfo || planInfo.canShareWithTeams;
59
+
55
60
  const adminNavItems = [
56
- { pathForLink: `${adminBaseLink}/members`, pathForActive: `${adminBaseFull}/members`, label: t('admin.users'), icon: Users },
57
- { pathForLink: `${adminBaseLink}/teams`, pathForActive: `${adminBaseFull}/teams`, label: t('admin.teams'), icon: UserCog },
61
+ ...(showAdminUsersAndTeams
62
+ ? [
63
+ { pathForLink: `${adminBaseLink}/members`, pathForActive: `${adminBaseFull}/members`, label: t('admin.users'), icon: Users },
64
+ { pathForLink: `${adminBaseLink}/teams`, pathForActive: `${adminBaseFull}/teams`, label: t('admin.teams'), icon: UserCog },
65
+ ]
66
+ : []),
58
67
  ...(!hideAdminOidcAndSmtp
59
68
  ? [
60
69
  { pathForLink: `${adminBaseLink}/oidc`, pathForActive: `${adminBaseFull}/oidc`, label: t('admin.oidcProviders'), icon: Key },
@@ -52,7 +52,7 @@ export default function GlobalSearch() {
52
52
  { type: 'navigation', title: t('folders.title'), path: `${prefix}/folders`.replace(/\/+/g, '/') || '/folders', id: 'nav-folders' },
53
53
  { type: 'navigation', title: t('tags.title'), path: `${prefix}/tags`.replace(/\/+/g, '/') || '/tags', id: 'nav-tags' },
54
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' }] : []),
55
+ ...(showAdmin ? [{ type: 'navigation' as const, title: t('admin.title'), path: `${prefix}/admin`.replace(/\/+/g, '/') || '/admin', id: 'nav-admin' }] : []),
56
56
  ], [showAdmin, t, prefix]);
57
57
 
58
58
  const actionItems: SearchResult[] = useMemo(() => [
@@ -67,7 +67,7 @@ export default function UserDropdown({ user }: UserDropdownProps) {
67
67
  </DropdownMenuItem>
68
68
  {showAdmin && (
69
69
  <DropdownMenuItem asChild>
70
- <Link to={`${prefix}/admin/members`} className="flex items-center gap-2 cursor-pointer">
70
+ <Link to={`${prefix}/admin`.replace(/\/+/g, '/') || '/admin'} className="flex items-center gap-2 cursor-pointer">
71
71
  <Settings className="h-4 w-4" />
72
72
  {t('admin.title')}
73
73
  </Link>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdguggenbichler/slugbase-core",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "SlugBase core: backend and frontend entrypoints for self-hosted and cloud apps",
5
5
  "type": "module",
6
6
  "exports": {