@mdguggenbichler/slugbase-core 0.0.28 → 0.0.29

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.
@@ -1 +1 @@
1
- {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAsIxB,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAuJxB,eAAe,MAAM,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { queryOne, execute } from '../db/index.js';
2
+ import { query, queryOne, execute } from '../db/index.js';
3
3
  import { requireAuth } from '../middleware/auth.js';
4
4
  import { validateEmail, normalizeEmail, validateLength, sanitizeString } from '../utils/validation.js';
5
5
  import { v4 as uuidv4 } from 'uuid';
@@ -7,6 +7,20 @@ import crypto from 'crypto';
7
7
  import { sendEmailVerificationEmail } from '../utils/email.js';
8
8
  const router = Router();
9
9
  router.use(requireAuth());
10
+ // List users for sharing (same tenant/org). No admin required. Used by sharing modal.
11
+ // Core: users table has no tenant_id; returns all users except current (self-hosted single-tenant).
12
+ router.get('/for-sharing', async (req, res) => {
13
+ const authReq = req;
14
+ try {
15
+ const userId = authReq.user.id;
16
+ const rows = await query('SELECT id, name, email FROM users WHERE id != ? ORDER BY name ASC, email ASC', [userId]);
17
+ const list = Array.isArray(rows) ? rows : (rows ? [rows] : []);
18
+ res.json(list);
19
+ }
20
+ catch (error) {
21
+ res.status(500).json({ error: error.message });
22
+ }
23
+ });
10
24
  // Get current user profile
11
25
  router.get('/me', async (req, res) => {
12
26
  const authReq = req;
@@ -1 +1 @@
1
- {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/utils/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMhD"}
1
+ {"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/utils/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAGhD"}
@@ -4,9 +4,6 @@ export function getDefaultTenantId() {
4
4
  }
5
5
  export function getTenantId(req) {
6
6
  const tenantId = req.tenantId;
7
- if (!tenantId) {
8
- throw new Error('Missing tenantId on request');
9
- }
10
- return tenantId;
7
+ return tenantId ?? DEFAULT_TENANT_ID;
11
8
  }
12
9
  //# sourceMappingURL=tenant.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tenant.js","sourceRoot":"","sources":["../../src/utils/tenant.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAE3C,MAAM,UAAU,kBAAkB;IAChC,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,MAAM,QAAQ,GAAI,GAAuC,CAAC,QAAQ,CAAC;IACnE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"tenant.js","sourceRoot":"","sources":["../../src/utils/tenant.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAE3C,MAAM,UAAU,kBAAkB;IAChC,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,MAAM,QAAQ,GAAI,GAAuC,CAAC,QAAQ,CAAC;IACnE,OAAO,QAAQ,IAAI,iBAAiB,CAAC;AACvC,CAAC"}
@@ -69,7 +69,7 @@ export default function AppSidebar({ user, version = null }: AppSidebarProps) {
69
69
  pathname === rootActivePath + '/' ||
70
70
  pathname === (pathBaseForActive || '/');
71
71
 
72
- const showAdmin = user?.is_admin;
72
+ const showAdmin = !!(user?.is_admin);
73
73
  const [adminOpen, setAdminOpen] = useState(() => {
74
74
  if (typeof window === 'undefined') return true;
75
75
  const stored = localStorage.getItem(SIDEBAR_ADMIN_OPEN_KEY);
@@ -31,7 +31,7 @@ export default function UserDropdown({ user }: UserDropdownProps) {
31
31
  const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
32
32
  const { logout } = useAuth();
33
33
 
34
- const showAdmin = user?.is_admin;
34
+ const showAdmin = !!(user?.is_admin);
35
35
 
36
36
  if (!user) return null;
37
37
 
@@ -1,6 +1,5 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { useAuth } from '../../contexts/AuthContext';
4
3
  import api from '../../api/client';
5
4
  import {
6
5
  Dialog,
@@ -12,15 +11,6 @@ import {
12
11
  import { Separator } from '../ui/separator';
13
12
  import { Input } from '../ui/input';
14
13
  import { ScrollArea } from '../ui/scroll-area';
15
- import { Popover, PopoverContent, PopoverAnchor, PopoverTrigger } from '../ui/popover';
16
- import {
17
- Command,
18
- CommandEmpty,
19
- CommandGroup,
20
- CommandInput,
21
- CommandItem,
22
- CommandList,
23
- } from '../ui/command';
24
14
  import Button from '../ui/Button';
25
15
  import Tooltip from '../ui/Tooltip';
26
16
  import { useToast } from '../ui/Toast';
@@ -56,7 +46,6 @@ export default function ShareResourceDialog({
56
46
  onSuccess,
57
47
  }: ShareResourceDialogProps) {
58
48
  const { t } = useTranslation();
59
- const { user } = useAuth();
60
49
  const { showToast } = useToast();
61
50
  const [loading, setLoading] = useState(true);
62
51
  const [saving, setSaving] = useState(false);
@@ -67,17 +56,16 @@ export default function ShareResourceDialog({
67
56
  const [allUsers, setAllUsers] = useState<SharedUser[]>([]);
68
57
  const [teams, setTeams] = useState<SharedTeam[]>([]);
69
58
  const [emailInput, setEmailInput] = useState('');
70
- const [peoplePopoverOpen, setPeoplePopoverOpen] = useState(false);
71
- const [teamsPopoverOpen, setTeamsPopoverOpen] = useState(false);
59
+ const [peopleDropdownOpen, setPeopleDropdownOpen] = useState(false);
72
60
  const [activeTab, setActiveTab] = useState<'people' | 'teams'>('people');
73
61
 
62
+ const MIN_CHARS_FOR_USER_DROPDOWN = 3;
63
+
74
64
  const allowShareToTeams = teams.length > 0;
75
65
  const allowShareToUsers = true;
76
66
 
77
- const fetchResource = useCallback(async () => {
78
- if (!resourceId || !isOpen) return;
79
- setLoading(true);
80
- setError(null);
67
+ const refreshResourceOnly = useCallback(async () => {
68
+ if (!resourceId) return;
81
69
  try {
82
70
  const endpoint = resourceType === 'bookmark' ? `/bookmarks/${resourceId}` : `/folders/${resourceId}`;
83
71
  const res = await api.get(endpoint);
@@ -85,35 +73,56 @@ export default function ShareResourceDialog({
85
73
  setSharedUsers(data.shared_users ?? []);
86
74
  setSharedTeams(data.shared_teams ?? []);
87
75
  setResourceData(resourceType === 'folder' ? { name: data.name, icon: data.icon } : null);
88
- } catch (err: any) {
89
- console.error('Failed to fetch resource:', err);
90
- setError(err.response?.data?.error || t('common.error'));
91
- showToast(t('common.error'), 'error');
92
- } finally {
93
- setLoading(false);
76
+ } catch {
77
+ // ignore
94
78
  }
95
- }, [resourceId, resourceType, isOpen, t, showToast]);
79
+ }, [resourceId, resourceType]);
96
80
 
97
- const fetchUsersAndTeams = useCallback(async () => {
98
- try {
99
- const [usersRes, teamsRes] = await Promise.all([
100
- api.get('/admin/users'),
101
- api.get('/teams'),
102
- ]);
103
- const users = Array.isArray(usersRes.data) ? usersRes.data : [];
104
- setAllUsers(users.filter((u: SharedUser) => u.id !== user?.id));
105
- setTeams(teamsRes.data ?? []);
106
- } catch (err) {
107
- console.error('Failed to fetch users/teams:', err);
81
+ const loadAll = useCallback(async () => {
82
+ if (!resourceId || !isOpen) return;
83
+ setLoading(true);
84
+ setError(null);
85
+ const endpoint = resourceType === 'bookmark' ? `/bookmarks/${resourceId}` : `/folders/${resourceId}`;
86
+
87
+ const [resourceResult, usersResult, teamsResult] = await Promise.allSettled([
88
+ api.get(endpoint),
89
+ (async () => {
90
+ try {
91
+ const res = await api.get('/users/for-sharing');
92
+ return Array.isArray(res.data) ? res.data : [];
93
+ } catch (e: any) {
94
+ try {
95
+ const adminRes = await api.get('/admin/users');
96
+ const list = Array.isArray(adminRes.data) ? adminRes.data : [];
97
+ return list.filter((u: SharedUser) => u.id && u.email);
98
+ } catch {
99
+ return [];
100
+ }
101
+ }
102
+ })(),
103
+ api.get('/teams').then((r) => r.data).catch(() => []),
104
+ ]);
105
+
106
+ if (resourceResult.status === 'fulfilled') {
107
+ const data = resourceResult.value.data;
108
+ setSharedUsers(data.shared_users ?? []);
109
+ setSharedTeams(data.shared_teams ?? []);
110
+ setResourceData(resourceType === 'folder' ? { name: data.name, icon: data.icon } : null);
111
+ } else {
112
+ setError(t('common.error'));
113
+ showToast(t('common.error'), 'error');
108
114
  }
109
- }, [user?.id]);
115
+
116
+ setAllUsers(usersResult.status === 'fulfilled' ? usersResult.value : []);
117
+ const teamsData = teamsResult.status === 'fulfilled' ? teamsResult.value : [];
118
+ setTeams(Array.isArray(teamsData) ? teamsData : teamsData != null ? [teamsData] : []);
119
+
120
+ setLoading(false);
121
+ }, [resourceId, resourceType, isOpen, t, showToast]);
110
122
 
111
123
  useEffect(() => {
112
- if (isOpen) {
113
- fetchResource();
114
- fetchUsersAndTeams();
115
- }
116
- }, [isOpen, fetchResource, fetchUsersAndTeams]);
124
+ if (isOpen) loadAll();
125
+ }, [isOpen, loadAll]);
117
126
 
118
127
  async function updateShares(userIds: string[], teamIds: string[], shareAllTeams: boolean) {
119
128
  setSaving(true);
@@ -130,7 +139,7 @@ export default function ShareResourceDialog({
130
139
  payload.share_all_teams = shareAllTeams;
131
140
  }
132
141
  await api.put(endpoint, payload);
133
- await fetchResource();
142
+ await refreshResourceOnly();
134
143
  onSuccess();
135
144
  showToast(t('common.success'), 'success');
136
145
  } catch (err: any) {
@@ -156,7 +165,7 @@ export default function ShareResourceDialog({
156
165
  const newUserIds = [...sharedUsers.map((u) => u.id), userId];
157
166
  if (newUserIds.includes(userId)) return;
158
167
  updateShares(newUserIds, sharedTeams.map((t) => t.id), false);
159
- setPeoplePopoverOpen(false);
168
+ setPeopleDropdownOpen(false);
160
169
  setEmailInput('');
161
170
  }
162
171
 
@@ -175,14 +184,19 @@ export default function ShareResourceDialog({
175
184
  const newTeamIds = [...sharedTeams.map((t) => t.id), teamId];
176
185
  if (newTeamIds.includes(teamId)) return;
177
186
  updateShares(sharedUsers.map((u) => u.id), newTeamIds, false);
178
- setTeamsPopoverOpen(false);
179
187
  }
180
188
 
189
+ const searchQuery = emailInput.trim().toLowerCase();
181
190
  const filteredUsers = allUsers.filter((u) => {
182
- if (!emailInput.trim()) return true;
183
- const q = emailInput.toLowerCase();
184
- return u.email.toLowerCase().includes(q) || (u.name && u.name.toLowerCase().includes(q));
191
+ if (!searchQuery) return false;
192
+ return (
193
+ u.email.toLowerCase().includes(searchQuery) ||
194
+ (u.name && u.name.toLowerCase().includes(searchQuery))
195
+ );
185
196
  });
197
+ const usersAvailableToAdd = filteredUsers.filter((u) => !sharedUsers.some((su) => su.id === u.id));
198
+ const showUserDropdown =
199
+ searchQuery.length >= MIN_CHARS_FOR_USER_DROPDOWN && usersAvailableToAdd.length > 0;
186
200
 
187
201
  const filteredTeams = teams.filter((t) => !sharedTeams.some((st) => st.id === t.id));
188
202
 
@@ -275,6 +289,11 @@ export default function ShareResourceDialog({
275
289
 
276
290
  <div>
277
291
  <h4 className="text-sm font-medium mb-3">{t('sharing.addAccess')}</h4>
292
+ {!loading && allUsers.length === 0 && teams.length === 0 && (
293
+ <p className="text-xs text-amber-600 dark:text-amber-500 mb-2" role="status">
294
+ {t('sharing.noUsersOrTeams')}
295
+ </p>
296
+ )}
278
297
  {allowShareToTeams && allowShareToUsers ? (
279
298
  <div className="flex gap-2 border-b mb-3">
280
299
  <button
@@ -306,105 +325,97 @@ export default function ShareResourceDialog({
306
325
 
307
326
  {allowShareToUsers && (activeTab === 'people' || !allowShareToTeams) && (
308
327
  <div className="space-y-2">
309
- <div className="flex gap-2">
310
- <Input
311
- placeholder={t('admin.searchUsers')}
312
- value={emailInput}
313
- onChange={(e) => setEmailInput(e.target.value)}
314
- onKeyDown={(e) => {
315
- if (e.key === 'Enter') {
316
- e.preventDefault();
317
- handleAddUserByEmail();
318
- }
319
- }}
320
- className="flex-1"
321
- />
322
- <Popover open={peoplePopoverOpen} onOpenChange={setPeoplePopoverOpen}>
323
- <PopoverAnchor asChild>
324
- <Button
325
- type="button"
326
- variant="outline"
327
- size="sm"
328
- disabled={saving}
329
- onClick={() => {
330
- if (emailInput.trim()) {
331
- handleAddUserByEmail();
332
- } else {
333
- setPeoplePopoverOpen(true);
334
- }
335
- }}
336
- >
337
- <UserPlus className="h-4 w-4" />
338
- {t('sharing.add')}
339
- </Button>
340
- </PopoverAnchor>
341
- <PopoverContent className="w-72 p-0" align="start">
342
- <Command>
343
- <CommandInput
344
- placeholder={t('admin.searchUsers')}
345
- value={emailInput}
346
- onValueChange={setEmailInput}
347
- />
348
- <CommandList>
349
- <CommandEmpty>{t('common.noResults')}</CommandEmpty>
350
- <CommandGroup>
351
- {filteredUsers
352
- .filter((u) => !sharedUsers.some((su) => su.id === u.id))
353
- .map((u) => (
354
- <CommandItem
355
- key={u.id}
356
- onSelect={() => handleAddUser(u.id)}
357
- className="flex flex-col items-start gap-0.5"
358
- >
359
- <span className="font-medium">{u.name || u.email}</span>
360
- {u.name && u.email && (
361
- <span className="text-xs text-muted-foreground">{u.email}</span>
362
- )}
363
- </CommandItem>
364
- ))}
365
- </CommandGroup>
366
- </CommandList>
367
- </Command>
368
- </PopoverContent>
369
- </Popover>
328
+ <div className="relative">
329
+ <div className="flex gap-2">
330
+ <Input
331
+ placeholder={t('admin.searchUsers')}
332
+ value={emailInput}
333
+ onChange={(e) => {
334
+ setEmailInput(e.target.value);
335
+ setPeopleDropdownOpen(true);
336
+ }}
337
+ onFocus={() => {
338
+ if (searchQuery.length >= MIN_CHARS_FOR_USER_DROPDOWN)
339
+ setPeopleDropdownOpen(true);
340
+ }}
341
+ onBlur={() => {
342
+ setTimeout(() => setPeopleDropdownOpen(false), 150);
343
+ }}
344
+ onKeyDown={(e) => {
345
+ if (e.key === 'Enter') {
346
+ e.preventDefault();
347
+ handleAddUserByEmail();
348
+ }
349
+ }}
350
+ className="flex-1"
351
+ autoComplete="off"
352
+ />
353
+ <Button
354
+ type="button"
355
+ variant="outline"
356
+ size="sm"
357
+ disabled={saving}
358
+ onClick={() => handleAddUserByEmail()}
359
+ >
360
+ <UserPlus className="h-4 w-4" />
361
+ {t('sharing.add')}
362
+ </Button>
363
+ </div>
364
+ {peopleDropdownOpen && showUserDropdown && (
365
+ <div
366
+ className="absolute top-full left-0 right-0 z-10 mt-1 max-h-48 overflow-auto rounded-md border bg-popover shadow-md"
367
+ role="listbox"
368
+ >
369
+ {usersAvailableToAdd.map((u) => (
370
+ <button
371
+ key={u.id}
372
+ type="button"
373
+ role="option"
374
+ className="flex w-full flex-col items-start gap-0.5 px-3 py-2 text-left text-sm hover:bg-accent focus:bg-accent focus:outline-none"
375
+ onMouseDown={(e) => {
376
+ e.preventDefault();
377
+ handleAddUser(u.id);
378
+ }}
379
+ >
380
+ <span className="font-medium">{u.name || u.email}</span>
381
+ {u.name && u.email && (
382
+ <span className="text-xs text-muted-foreground">{u.email}</span>
383
+ )}
384
+ </button>
385
+ ))}
386
+ </div>
387
+ )}
370
388
  </div>
371
- <p className="text-xs text-muted-foreground">{t('sharing.emailNotAssociated')}</p>
389
+ <p className="text-xs text-muted-foreground">
390
+ {t('sharing.typeToSearchUsers', { count: MIN_CHARS_FOR_USER_DROPDOWN })}
391
+ </p>
372
392
  </div>
373
393
  )}
374
394
 
375
395
  {allowShareToTeams && (activeTab === 'teams' || !allowShareToUsers) && (
376
- <Popover open={teamsPopoverOpen} onOpenChange={setTeamsPopoverOpen}>
377
- <PopoverTrigger asChild>
378
- <Button
379
- type="button"
380
- variant="outline"
381
- size="sm"
382
- disabled={saving}
383
- className="w-full justify-start"
384
- >
385
- <Users className="h-4 w-4 mr-2" />
386
- {t('sharing.teams')}
387
- </Button>
388
- </PopoverTrigger>
389
- <PopoverContent className="w-72 p-0" align="start">
390
- <Command>
391
- <CommandInput placeholder={t('admin.searchTeams')} />
392
- <CommandList>
393
- <CommandEmpty>{t('common.noResults')}</CommandEmpty>
394
- <CommandGroup>
395
- {filteredTeams.map((team) => (
396
- <CommandItem
397
- key={team.id}
398
- onSelect={() => handleAddTeam(team.id)}
399
- >
400
- {team.name}
401
- </CommandItem>
402
- ))}
403
- </CommandGroup>
404
- </CommandList>
405
- </Command>
406
- </PopoverContent>
407
- </Popover>
396
+ <div className="space-y-2">
397
+ <p className="text-xs text-muted-foreground">{t('sharing.selectTeamToAdd')}</p>
398
+ {filteredTeams.length === 0 ? (
399
+ <p className="text-sm text-muted-foreground py-2">{t('common.noResults')}</p>
400
+ ) : (
401
+ <ScrollArea className="max-h-48 rounded-md border">
402
+ <div className="p-1 space-y-0.5">
403
+ {filteredTeams.map((team) => (
404
+ <button
405
+ key={team.id}
406
+ type="button"
407
+ disabled={saving}
408
+ className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-accent focus:bg-accent focus:outline-none disabled:opacity-50"
409
+ onClick={() => handleAddTeam(team.id)}
410
+ >
411
+ <Users className="h-4 w-4 shrink-0 text-muted-foreground" />
412
+ <span>{team.name}</span>
413
+ </button>
414
+ ))}
415
+ </div>
416
+ </ScrollArea>
417
+ )}
418
+ </div>
408
419
  )}
409
420
  </div>
410
421
  </div>
@@ -29,18 +29,21 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
29
29
 
30
30
  const defaultIconClass = 'h-4 w-4';
31
31
 
32
- export default function Button({
33
- variant = 'primary',
34
- size = 'md',
35
- icon: Icon,
36
- iconPosition = 'left',
37
- iconClassName = defaultIconClass,
38
- loading = false,
39
- children,
40
- className = '',
41
- disabled,
42
- ...props
43
- }: ButtonProps) {
32
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
33
+ {
34
+ variant = 'primary',
35
+ size = 'md',
36
+ icon: Icon,
37
+ iconPosition = 'left',
38
+ iconClassName = defaultIconClass,
39
+ loading = false,
40
+ children,
41
+ className = '',
42
+ disabled,
43
+ ...props
44
+ },
45
+ ref
46
+ ) {
44
47
  const iconClass = iconClassName || defaultIconClass;
45
48
  const content = loading ? (
46
49
  <Loader2 className="h-4 w-4 animate-spin" />
@@ -54,6 +57,7 @@ export default function Button({
54
57
 
55
58
  return (
56
59
  <ShadcnButton
60
+ ref={ref}
57
61
  className={cn(className)}
58
62
  variant={variantMap[variant]}
59
63
  size={sizeMap[size]}
@@ -63,6 +67,9 @@ export default function Button({
63
67
  {content}
64
68
  </ShadcnButton>
65
69
  );
66
- }
70
+ });
71
+ Button.displayName = 'Button';
72
+
73
+ export default Button;
67
74
 
68
75
  export { buttonVariants };
@@ -1,6 +1,5 @@
1
1
  import { useState, useEffect } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { useAuth } from '../../contexts/AuthContext';
4
3
  import api from '../../api/client';
5
4
  import { Popover, PopoverContent, PopoverTrigger } from './popover';
6
5
  import { Switch } from './switch';
@@ -48,7 +47,6 @@ export function SharingField({
48
47
  disabled = false,
49
48
  }: SharingFieldProps) {
50
49
  const { t } = useTranslation();
51
- const { user } = useAuth();
52
50
  const [popoverOpen, setPopoverOpen] = useState(false);
53
51
  const [allUsers, setAllUsers] = useState<UserType[]>([]);
54
52
  const [userSearchQuery, setUserSearchQuery] = useState('');
@@ -62,11 +60,12 @@ export function SharingField({
62
60
 
63
61
  async function loadUsers() {
64
62
  try {
65
- const response = await api.get('/admin/users');
63
+ // Use non-admin endpoint so any user can add people when sharing (same org/tenant).
64
+ const response = await api.get('/users/for-sharing');
66
65
  const users = Array.isArray(response.data) ? response.data : [];
67
- setAllUsers(users.filter((u: UserType) => u.id !== user?.id));
66
+ setAllUsers(users);
68
67
  } catch (error) {
69
- console.error('Failed to load users:', error);
68
+ console.error('Failed to load users for sharing:', error);
70
69
  }
71
70
  }
72
71
 
@@ -387,7 +387,10 @@
387
387
  "notSharedYet": "Not shared yet",
388
388
  "emailNotAssociated": "This email is not associated with a SlugBase user yet.",
389
389
  "removeAccess": "Remove access",
390
- "add": "Add"
390
+ "add": "Add",
391
+ "noUsersOrTeams": "No users or teams could be loaded. Check the Network tab for /api/users/for-sharing and /api/teams.",
392
+ "typeToSearchUsers": "Type at least {{count}} characters to see matching users.",
393
+ "selectTeamToAdd": "Select a team to share with."
391
394
  },
392
395
  "plan": {
393
396
  "limitBookmarks": "You've reached the Free plan limit ({{limit}} bookmarks). Upgrade to add more.",
@@ -180,7 +180,7 @@ export default function Bookmarks() {
180
180
 
181
181
  async function loadData() {
182
182
  try {
183
- const [bookmarksRes, foldersRes, tagsRes, teamsRes] = await Promise.all([
183
+ const [bookmarksSettled, foldersSettled, tagsSettled, teamsSettled] = await Promise.allSettled([
184
184
  api.get('/bookmarks', {
185
185
  params: {
186
186
  folder_id: selectedFolder || undefined,
@@ -197,13 +197,15 @@ export default function Bookmarks() {
197
197
  api.get('/tags'),
198
198
  api.get('/teams'),
199
199
  ]);
200
- const payload = bookmarksRes.data;
201
- const items = payload.items ?? [];
202
- setTotal(payload.total ?? 0);
203
- setBookmarks(items);
204
- setFolders(foldersRes.data);
205
- setTags(tagsRes.data);
206
- setTeams(teamsRes.data);
200
+ if (bookmarksSettled.status === 'fulfilled') {
201
+ const payload = bookmarksSettled.value.data;
202
+ const items = payload.items ?? [];
203
+ setTotal(payload.total ?? 0);
204
+ setBookmarks(items);
205
+ }
206
+ if (foldersSettled.status === 'fulfilled') setFolders(foldersSettled.value.data ?? []);
207
+ if (tagsSettled.status === 'fulfilled') setTags(tagsSettled.value.data ?? []);
208
+ if (teamsSettled.status === 'fulfilled') setTeams(Array.isArray(teamsSettled.value.data) ? teamsSettled.value.data : []);
207
209
  } catch (error) {
208
210
  console.error('Failed to load data:', error);
209
211
  } finally {
@@ -498,7 +500,7 @@ export default function Bookmarks() {
498
500
  return (
499
501
  <div className="space-y-6 pb-24">
500
502
  {/* Sticky controls bar: header + filters/toolbar - stays visible when scrolling */}
501
- <div className="sticky top-0 z-40 space-y-4 pb-4 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 pt-0 -mt-8 bg-background border-b shadow-sm">
503
+ <div className="sticky top-0 z-40 space-y-4 pb-4 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 pt-0 -mt-8 bg-background shadow-sm">
502
504
  <PageHeader
503
505
  className="pt-4"
504
506
  title={`${t('bookmarks.title')} (${total})`}
@@ -197,7 +197,7 @@ export default function Folders() {
197
197
  return (
198
198
  <div className="space-y-6 pb-24">
199
199
  {/* Sticky controls bar: header + toolbar - stays visible when scrolling */}
200
- <div className="sticky top-0 z-40 space-y-4 pb-4 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 pt-0 -mt-8 bg-background border-b shadow-sm">
200
+ <div className="sticky top-0 z-40 space-y-4 pb-4 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 pt-0 -mt-8 bg-background shadow-sm">
201
201
  <PageHeader
202
202
  className="pt-4"
203
203
  title={`${t('folders.title')} (${totalFolders})`}
@@ -226,7 +226,7 @@ export default function Profile() {
226
226
  <span>
227
227
  {t('profile.signedInAs')}: <span className="text-foreground">{user.email}</span>
228
228
  </span>
229
- {user.is_admin && (
229
+ {!!user.is_admin && (
230
230
  <Badge variant="secondary" className="text-xs font-normal">
231
231
  {t('profile.admin')}
232
232
  </Badge>
@@ -172,7 +172,7 @@ export default function Tags() {
172
172
  return (
173
173
  <div className="space-y-6 pb-24">
174
174
  {/* Sticky controls bar: header + toolbar - stays visible when scrolling */}
175
- <div className="sticky top-0 z-40 space-y-4 pb-4 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 pt-0 -mt-8 bg-background border-b shadow-sm">
175
+ <div className="sticky top-0 z-40 space-y-4 pb-4 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 pt-0 -mt-8 bg-background shadow-sm">
176
176
  <PageHeader
177
177
  className="pt-4"
178
178
  title={`${t('tags.title')} (${totalTags})`}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdguggenbichler/slugbase-core",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "SlugBase core: backend and frontend entrypoints for self-hosted and cloud apps",
5
5
  "type": "module",
6
6
  "exports": {