@kyro-cms/admin 0.9.0 → 0.9.1
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/dist/index.cjs +11960 -11006
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +563 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +12183 -11238
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/components/ActionBar.tsx +27 -14
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AutoForm.tsx +585 -369
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +71 -56
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +33 -20
- package/src/components/MediaGallery.tsx +219 -194
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +7 -7
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +430 -50
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipField.tsx +153 -87
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/SlidePanel.tsx +8 -3
- package/src/components/ui/icons.tsx +109 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/hooks/useAutoFormState.ts +125 -62
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AuthLayout.astro +14 -2
- package/src/lib/autoform-store.ts +85 -52
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +44 -9
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
// Re-exported from react
|
|
2
|
-
// Allows
|
|
1
|
+
// Re-exported from lucide-react (ISC license)
|
|
2
|
+
// Allows swapping individual icons without touching consumers.
|
|
3
3
|
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
export {
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
export {
|
|
35
|
-
export {
|
|
36
|
-
export {
|
|
37
|
-
export {
|
|
38
|
-
export {
|
|
39
|
-
export {
|
|
40
|
-
export {
|
|
41
|
-
export {
|
|
42
|
-
export {
|
|
43
|
-
export {
|
|
44
|
-
export {
|
|
45
|
-
export {
|
|
46
|
-
export {
|
|
47
|
-
export {
|
|
48
|
-
export {
|
|
49
|
-
export {
|
|
50
|
-
export {
|
|
51
|
-
export {
|
|
52
|
-
export {
|
|
53
|
-
export {
|
|
54
|
-
export {
|
|
55
|
-
export {
|
|
56
|
-
export {
|
|
57
|
-
export {
|
|
58
|
-
export {
|
|
59
|
-
export {
|
|
60
|
-
export {
|
|
61
|
-
export {
|
|
62
|
-
export {
|
|
63
|
-
export {
|
|
64
|
-
export {
|
|
65
|
-
export {
|
|
66
|
-
export {
|
|
67
|
-
export {
|
|
68
|
-
export {
|
|
69
|
-
export {
|
|
70
|
-
export {
|
|
71
|
-
export {
|
|
72
|
-
export {
|
|
73
|
-
export {
|
|
74
|
-
export {
|
|
75
|
-
export {
|
|
76
|
-
export {
|
|
77
|
-
export {
|
|
78
|
-
export {
|
|
79
|
-
export {
|
|
80
|
-
export {
|
|
81
|
-
export {
|
|
82
|
-
export {
|
|
83
|
-
export {
|
|
84
|
-
export {
|
|
85
|
-
export {
|
|
86
|
-
export {
|
|
87
|
-
export {
|
|
88
|
-
export {
|
|
89
|
-
export {
|
|
90
|
-
export {
|
|
91
|
-
export {
|
|
92
|
-
export {
|
|
93
|
-
export {
|
|
94
|
-
export {
|
|
95
|
-
export {
|
|
96
|
-
export {
|
|
97
|
-
export {
|
|
98
|
-
export {
|
|
99
|
-
export {
|
|
100
|
-
export {
|
|
101
|
-
export {
|
|
102
|
-
export {
|
|
103
|
-
export {
|
|
104
|
-
export {
|
|
105
|
-
export {
|
|
106
|
-
export {
|
|
107
|
-
export {
|
|
108
|
-
export {
|
|
4
|
+
export { Activity as IconActivity } from "lucide-react";
|
|
5
|
+
export { TriangleAlert as IconAlertTriangle } from "lucide-react";
|
|
6
|
+
export { AlignLeft as IconAlignLeft } from "lucide-react";
|
|
7
|
+
export { Archive as IconArchive } from "lucide-react";
|
|
8
|
+
export { ArrowDown as IconArrowDown } from "lucide-react";
|
|
9
|
+
export { ArrowRight as IconArrowRight } from "lucide-react";
|
|
10
|
+
export { ArrowUpRight as IconArrowUpRight } from "lucide-react";
|
|
11
|
+
export { Blocks as IconBlocks } from "lucide-react";
|
|
12
|
+
export { Bold as IconBold } from "lucide-react";
|
|
13
|
+
export { Book as IconBook } from "lucide-react";
|
|
14
|
+
export { Box as IconBox } from "lucide-react";
|
|
15
|
+
export { CircleCheck as IconCheckCircle2 } from "lucide-react";
|
|
16
|
+
export { Check as IconCheck } from "lucide-react";
|
|
17
|
+
export { ChevronDown as IconChevronDown } from "lucide-react";
|
|
18
|
+
export { ChevronLeft as IconChevronLeft } from "lucide-react";
|
|
19
|
+
export { ChevronRight as IconChevronRight } from "lucide-react";
|
|
20
|
+
export { ChevronUp as IconChevronUp } from "lucide-react";
|
|
21
|
+
export { Clock as IconClock } from "lucide-react";
|
|
22
|
+
export { Code as IconCode } from "lucide-react";
|
|
23
|
+
export { CodeXml as IconCode2 } from "lucide-react";
|
|
24
|
+
export { Columns3 as IconColumns3 } from "lucide-react";
|
|
25
|
+
export { Copy as IconCopy } from "lucide-react";
|
|
26
|
+
export { Crop as IconCrop } from "lucide-react";
|
|
27
|
+
export { Database as IconDatabase } from "lucide-react";
|
|
28
|
+
export { Download as IconDownload } from "lucide-react";
|
|
29
|
+
export { CloudDownload as IconDownloadCloud } from "lucide-react";
|
|
30
|
+
export { ExternalLink as IconExternalLink } from "lucide-react";
|
|
31
|
+
export { Eye as IconEye } from "lucide-react";
|
|
32
|
+
export { EyeOff as IconEyeOff } from "lucide-react";
|
|
33
|
+
export { File as IconFile } from "lucide-react";
|
|
34
|
+
export { Globe as IconGlobe } from "lucide-react";
|
|
35
|
+
export { Film as IconFilm } from "lucide-react";
|
|
36
|
+
export { Filter as IconFilter } from "lucide-react";
|
|
37
|
+
export { Folder as IconFolder } from "lucide-react";
|
|
38
|
+
export { FolderInput as IconFolderInput } from "lucide-react";
|
|
39
|
+
export { FolderPlus as IconFolderPlus } from "lucide-react";
|
|
40
|
+
export { Grid3X3 as IconGrid } from "lucide-react";
|
|
41
|
+
export { GripVertical as IconGripVertical } from "lucide-react";
|
|
42
|
+
export { Heading1 as IconHeading1 } from "lucide-react";
|
|
43
|
+
export { Hexagon as IconHexagon } from "lucide-react";
|
|
44
|
+
export { House as IconHome } from "lucide-react";
|
|
45
|
+
export { Image as IconImage } from "lucide-react";
|
|
46
|
+
export { Info as IconInfo } from "lucide-react";
|
|
47
|
+
export { Italic as IconItalic } from "lucide-react";
|
|
48
|
+
export { Key as IconKey } from "lucide-react";
|
|
49
|
+
export { LayoutPanelTop as IconLayout } from "lucide-react";
|
|
50
|
+
export { LayoutDashboard as IconLayoutDashboard } from "lucide-react";
|
|
51
|
+
export { Link as IconLink } from "lucide-react";
|
|
52
|
+
export { Link2 as IconLink2 } from "lucide-react";
|
|
53
|
+
export { List as IconList } from "lucide-react";
|
|
54
|
+
export { ListOrdered as IconListOrdered } from "lucide-react";
|
|
55
|
+
export { LoaderCircle as IconLoader2 } from "lucide-react";
|
|
56
|
+
export { Lock as IconLock } from "lucide-react";
|
|
57
|
+
export { LogOut as IconLogOut } from "lucide-react";
|
|
58
|
+
export { Mail as IconMail } from "lucide-react";
|
|
59
|
+
export { Maximize2 as IconMaximize2 } from "lucide-react";
|
|
60
|
+
export { Menu as IconMenu } from "lucide-react";
|
|
61
|
+
export { Minus as IconMinus } from "lucide-react";
|
|
62
|
+
export { Monitor as IconMonitor } from "lucide-react";
|
|
63
|
+
export { Moon as IconMoon } from "lucide-react";
|
|
64
|
+
export { EllipsisVertical as IconMoreVertical } from "lucide-react";
|
|
65
|
+
export { MousePointerClick as IconMousePointerClick } from "lucide-react";
|
|
66
|
+
export { Music as IconMusic } from "lucide-react";
|
|
67
|
+
export { Network as IconNetwork } from "lucide-react";
|
|
68
|
+
export { Palette as IconPalette } from "lucide-react";
|
|
69
|
+
export { Pause as IconPause } from "lucide-react";
|
|
70
|
+
export { Pencil as IconPencil } from "lucide-react";
|
|
71
|
+
export { Play as IconPlay } from "lucide-react";
|
|
72
|
+
export { CirclePlay as IconPlayCircle } from "lucide-react";
|
|
73
|
+
export { Plus as IconPlus } from "lucide-react";
|
|
74
|
+
export { Redo as IconRedo } from "lucide-react";
|
|
75
|
+
export { RefreshCcw as IconRefreshCcw } from "lucide-react";
|
|
76
|
+
export { RefreshCw as IconRefreshCw } from "lucide-react";
|
|
77
|
+
export { Save as IconSave } from "lucide-react";
|
|
78
|
+
export { Search as IconSearch } from "lucide-react";
|
|
79
|
+
export { Send as IconSend } from "lucide-react";
|
|
80
|
+
export { Settings as IconSettings } from "lucide-react";
|
|
81
|
+
export { Shield as IconShield } from "lucide-react";
|
|
82
|
+
export { ShieldCheck as IconShieldCheck } from "lucide-react";
|
|
83
|
+
export { Sparkles as IconSparkles } from "lucide-react";
|
|
84
|
+
export { Star as IconStar } from "lucide-react";
|
|
85
|
+
export { Strikethrough as IconStrikethrough } from "lucide-react";
|
|
86
|
+
export { Sun as IconSun } from "lucide-react";
|
|
87
|
+
export { Tag as IconTag } from "lucide-react";
|
|
88
|
+
export { Terminal as IconTerminal } from "lucide-react";
|
|
89
|
+
export { ToggleLeft as IconToggleLeft } from "lucide-react";
|
|
90
|
+
export { ToggleRight as IconToggleRight } from "lucide-react";
|
|
91
|
+
export { Trash2 as IconTrash2 } from "lucide-react";
|
|
92
|
+
export { TrendingUp as IconTrendingUp } from "lucide-react";
|
|
93
|
+
export { Type as IconType } from "lucide-react";
|
|
94
|
+
export { Underline as IconUnderline } from "lucide-react";
|
|
95
|
+
export { Undo as IconUndo } from "lucide-react";
|
|
96
|
+
export { LockOpen as IconUnlock } from "lucide-react";
|
|
97
|
+
export { User as IconUser } from "lucide-react";
|
|
98
|
+
export { UserPlus as IconUserPlus } from "lucide-react";
|
|
99
|
+
export { Users as IconUsers } from "lucide-react";
|
|
100
|
+
export { Video as IconVideo } from "lucide-react";
|
|
101
|
+
export { Webhook as IconWebhook } from "lucide-react";
|
|
102
|
+
export { X as IconX } from "lucide-react";
|
|
103
|
+
export { Zap as IconZap } from "lucide-react";
|
|
104
|
+
export { Dot as IconDot } from "lucide-react";
|
|
105
|
+
export { ShieldAlert as IconShieldAlert } from "lucide-react";
|
|
106
|
+
export { Pencil as IconEdit2 } from "lucide-react";
|
|
107
|
+
export { Calendar as IconCalendar } from "lucide-react";
|
|
108
|
+
export { Grid3X3 as IconGrid3X3 } from "lucide-react";
|
|
109
109
|
|
|
110
110
|
// Direct re-exports for files that still use original lucide-react names
|
|
111
|
-
export {
|
|
112
|
-
export {
|
|
111
|
+
export { Activity as Activity, AlignLeft as AlignLeft, Archive as Archive, ArrowDown as ArrowDown, ArrowRight as ArrowRight, ArrowUpRight as ArrowUpRight, Blocks as Blocks, Box as Box, Calendar as Calendar, Check as Check, ChevronDown as ChevronDown, ChevronLeft as ChevronLeft, ChevronRight as ChevronRight, ChevronUp as ChevronUp, Clock as Clock, Code as Code, Columns3 as Columns3, Copy as Copy, Crop as Crop, Download as Download, ExternalLink as ExternalLink, Eye as Eye, EyeOff as EyeOff, File as File, File as FileIcon, FileText as FileText, Globe as Globe, Film as Film, Filter as Filter, Folder as Folder, FolderInput as FolderInput, FolderPlus as FolderPlus, GripVertical as GripVertical, Heading1 as Heading1, Image as Image, Info as Info, Key as Key, LayoutDashboard as LayoutDashboard, Link as Link, Link2 as Link2, List as List, ListOrdered as ListOrdered, Lock as Lock, Mail as Mail, Maximize2 as Maximize2, Menu as Menu, Minus as Minus, Monitor as Monitor, MousePointerClick as MousePointerClick, Music as Music, Palette as Palette, Pause as Pause, Play as Play, Plus as Plus, RefreshCcw as RefreshCcw, RefreshCw as RefreshCw, Save as Save, Search as Search, Send as Send, Settings as Settings, Shield as Shield, Sparkles as Sparkles, Star as Star, Tag as Tag, Terminal as Terminal, ToggleLeft as ToggleLeft, ToggleRight as ToggleRight, Trash2 as Trash2, TrendingUp as TrendingUp, Type as Type, User as User, UserPlus as UserPlus, Users as Users, Video as Video, Webhook as Webhook, X as X, Zap as Zap, CircleHelp as HelpCircle } from "lucide-react";
|
|
112
|
+
export { CircleCheck as CheckCircle2, Grid3X3 as Grid, House as Home, LayoutPanelTop as Layout, LoaderCircle as Loader2, LockOpen as Unlock, CirclePlay as PlayCircle, TriangleAlert as AlertTriangle, CodeXml as Code2, CloudDownload as DownloadCloud, EllipsisVertical as MoreVertical, ShieldCheck as ShieldCheck, ShieldAlert as ShieldAlert, Pencil as Edit2, Moon as Moon, Sun as Sun, LogOut as LogOut, Database as Database, Hexagon as Hexagon, Network as Network, Book as Book, Bold as Bold, Italic as Italic, Underline as Underline, Strikethrough as Strikethrough, Undo as Undo, Redo as Redo, Dot as Dot, Grid3X3, Laptop as Laptop, Smartphone as Smartphone } from "lucide-react";
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { apiGet } from "../../lib/api";
|
|
3
|
+
import { useUIStore, toast } from "../../lib/stores";
|
|
4
|
+
import { X } from "../ui/icons";
|
|
5
|
+
import { UploadField } from "../fields/UploadField";
|
|
3
6
|
|
|
4
7
|
interface User {
|
|
5
8
|
id: string;
|
|
6
9
|
name?: string;
|
|
7
10
|
email: string;
|
|
8
11
|
role: string;
|
|
12
|
+
avatar?: string;
|
|
9
13
|
tenantId?: string;
|
|
10
14
|
emailVerified?: boolean;
|
|
11
15
|
locked?: boolean;
|
|
@@ -33,12 +37,41 @@ const roleOptions = [
|
|
|
33
37
|
export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
34
38
|
const [name, setName] = useState(user.name || "");
|
|
35
39
|
const [role, setRole] = useState(user.role);
|
|
40
|
+
const [avatar, setAvatar] = useState<string | undefined>(user.avatar);
|
|
41
|
+
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
|
42
|
+
const [avatarMedia, setAvatarMedia] = useState<any>(user.avatar ? { id: user.avatar } : null);
|
|
36
43
|
const [saving, setSaving] = useState(false);
|
|
37
44
|
const { confirm, alert } = useUIStore();
|
|
38
45
|
const [deleting, setDeleting] = useState(false);
|
|
39
46
|
const [locking, setLocking] = useState(false);
|
|
40
47
|
const [isLocked, setIsLocked] = useState(user.locked || false);
|
|
41
48
|
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (typeof avatar === "string" && /^[0-9a-f-]+$/i.test(avatar)) {
|
|
51
|
+
apiGet<any>(`/api/media/${avatar}`)
|
|
52
|
+
.then((media) => {
|
|
53
|
+
setAvatarUrl(media?.thumbnailUrl || media?.url || null);
|
|
54
|
+
setAvatarMedia({ id: avatar, url: media.url, thumbnailUrl: media.thumbnailUrl, filename: media.filename, originalName: media.originalName, mimeType: media.mimeType });
|
|
55
|
+
})
|
|
56
|
+
.catch(() => { setAvatarUrl(null); setAvatarMedia(null); });
|
|
57
|
+
} else {
|
|
58
|
+
setAvatarUrl(null);
|
|
59
|
+
setAvatarMedia(null);
|
|
60
|
+
}
|
|
61
|
+
}, [avatar]);
|
|
62
|
+
|
|
63
|
+
const handleAvatarChange = (val: any) => {
|
|
64
|
+
if (val && typeof val === "object") {
|
|
65
|
+
setAvatar(val.id);
|
|
66
|
+
setAvatarUrl(val.url || val.thumbnailUrl || null);
|
|
67
|
+
setAvatarMedia(val);
|
|
68
|
+
} else {
|
|
69
|
+
setAvatar(undefined);
|
|
70
|
+
setAvatarUrl(null);
|
|
71
|
+
setAvatarMedia(null);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
42
75
|
const handleSave = async () => {
|
|
43
76
|
setSaving(true);
|
|
44
77
|
try {
|
|
@@ -50,6 +83,9 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
50
83
|
if (name !== user.name) {
|
|
51
84
|
body.name = name.trim() === "" ? null : name.trim();
|
|
52
85
|
}
|
|
86
|
+
if (avatar !== user.avatar) {
|
|
87
|
+
body.avatar = avatar || null;
|
|
88
|
+
}
|
|
53
89
|
|
|
54
90
|
if (Object.keys(body).length === 0) {
|
|
55
91
|
window.location.href = adminPath + "/users";
|
|
@@ -66,12 +102,13 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
66
102
|
const data = await res.json();
|
|
67
103
|
|
|
68
104
|
if (res.ok) {
|
|
105
|
+
toast.success("User updated");
|
|
69
106
|
window.location.href = adminPath + "/users";
|
|
70
107
|
} else {
|
|
71
|
-
|
|
108
|
+
toast.error(data.error || "Failed to save user");
|
|
72
109
|
}
|
|
73
110
|
} catch (e) {
|
|
74
|
-
|
|
111
|
+
toast.error("Failed to save user");
|
|
75
112
|
} finally {
|
|
76
113
|
setSaving(false);
|
|
77
114
|
}
|
|
@@ -96,9 +133,12 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
96
133
|
});
|
|
97
134
|
if (res.ok) {
|
|
98
135
|
setIsLocked(!isLocked);
|
|
136
|
+
toast.success(isLocked ? "User unlocked" : "User locked");
|
|
137
|
+
} else {
|
|
138
|
+
toast.error("Failed to update lock status");
|
|
99
139
|
}
|
|
100
140
|
} catch (e) {
|
|
101
|
-
|
|
141
|
+
toast.error("Failed to toggle lock");
|
|
102
142
|
} finally {
|
|
103
143
|
setLocking(false);
|
|
104
144
|
}
|
|
@@ -119,10 +159,13 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
119
159
|
credentials: "include",
|
|
120
160
|
});
|
|
121
161
|
if (res.ok) {
|
|
162
|
+
toast.success("User deleted");
|
|
122
163
|
window.location.href = adminPath + "/users";
|
|
164
|
+
} else {
|
|
165
|
+
toast.error("Failed to delete user");
|
|
123
166
|
}
|
|
124
167
|
} catch (e) {
|
|
125
|
-
|
|
168
|
+
toast.error("Failed to delete user");
|
|
126
169
|
} finally {
|
|
127
170
|
setDeleting(false);
|
|
128
171
|
}
|
|
@@ -139,14 +182,18 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
139
182
|
<div className="flex-1 overflow-y-auto space-y-8">
|
|
140
183
|
<div className="surface-tile p-6 flex items-center justify-between">
|
|
141
184
|
<div className="flex items-center gap-4">
|
|
142
|
-
<div className="w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl">
|
|
143
|
-
{
|
|
185
|
+
<div className="relative w-14 h-14 rounded-full bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] flex items-center justify-center font-bold text-xl overflow-hidden flex-shrink-0">
|
|
186
|
+
{avatarUrl ? (
|
|
187
|
+
<img src={avatarUrl} alt="" className="w-full h-full object-cover" />
|
|
188
|
+
) : (
|
|
189
|
+
(name || user.email).charAt(0).toUpperCase()
|
|
190
|
+
)}
|
|
144
191
|
</div>
|
|
145
192
|
<div>
|
|
146
193
|
<h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]">
|
|
147
194
|
{name || user.email}
|
|
148
195
|
</h1>
|
|
149
|
-
<p className="text-sm text-[var(--kyro-text-secondary)] font-medium">
|
|
196
|
+
<p className="text-sm text-[var(--kyro-text-secondary)] font-medium mb-2">
|
|
150
197
|
{name ? user.email : `User ID: ${user.id}`}
|
|
151
198
|
</p>
|
|
152
199
|
</div>
|
|
@@ -248,14 +295,20 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
248
295
|
: "Never"}
|
|
249
296
|
</p>
|
|
250
297
|
</div>
|
|
251
|
-
<div>
|
|
252
|
-
<label className="text-xs font-bold text-[var(--kyro-text-secondary)]
|
|
253
|
-
|
|
298
|
+
<div className="col-span-2">
|
|
299
|
+
<label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
|
|
300
|
+
Photo
|
|
254
301
|
</label>
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
302
|
+
<div className="mt-1 flex items-center gap-2">
|
|
303
|
+
<UploadField
|
|
304
|
+
field={{ label: "Photo", name: "avatar", maxCount: 1, allowedTypes: ["image/*"] }}
|
|
305
|
+
value={avatarMedia}
|
|
306
|
+
onChange={handleAvatarChange}
|
|
307
|
+
disabled={saving}
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
258
310
|
</div>
|
|
311
|
+
|
|
259
312
|
<div>
|
|
260
313
|
<label className="text-xs font-bold text-[#64748b] tracking-wider">
|
|
261
314
|
Created
|
|
@@ -264,6 +317,16 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
264
317
|
{formatDate(user.createdAt)}
|
|
265
318
|
</p>
|
|
266
319
|
</div>
|
|
320
|
+
|
|
321
|
+
<div>
|
|
322
|
+
<label className="text-xs font-bold text-[var(--kyro-text-secondary)] tracking-wider">
|
|
323
|
+
Failed Attempts
|
|
324
|
+
</label>
|
|
325
|
+
<p className="mt-1 text-sm font-medium text-[var(--kyro-text-primary)]">
|
|
326
|
+
{user.failedLoginAttempts || 0}
|
|
327
|
+
</p>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
267
330
|
<div>
|
|
268
331
|
<label className="text-xs font-bold text-[#64748b] tracking-wider">
|
|
269
332
|
Updated
|
|
@@ -278,7 +341,7 @@ export function UserDetail({ user, apiPath, adminPath }: UserDetailProps) {
|
|
|
278
341
|
<button
|
|
279
342
|
onClick={handleSave}
|
|
280
343
|
disabled={saving}
|
|
281
|
-
className="px-6 py-2
|
|
344
|
+
className="kyro-btn kyro-btn-primary px-6 py-2 rounded-xl text-sm font-bold transition-colors disabled:opacity-50"
|
|
282
345
|
>
|
|
283
346
|
{saving ? "Saving…" : "Save Changes"}
|
|
284
347
|
</button>
|