@kyro-cms/admin 0.9.1 → 0.9.3
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 +1196 -1727
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +891 -1422
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar.tsx +25 -174
- package/src/components/Admin.tsx +1 -3
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +160 -265
- package/src/components/DetailView.tsx +38 -66
- package/src/components/FieldRenderer.tsx +1 -1
- package/src/components/ListView.tsx +26 -198
- package/src/components/MediaGallery.tsx +117 -175
- package/src/components/RestPlayground.tsx +54 -47
- package/src/components/fields/BlocksField.tsx +8 -10
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +2 -3
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +2 -10
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +1 -0
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +89 -161
- package/src/hooks/useQueue.ts +60 -0
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +66 -18
- package/src/lib/autoform-store.ts +6 -2
- package/src/lib/globals.ts +5 -3
- package/src/pages/auth/register.astro +5 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const dir = '/Users/macbook/Dev/Web/Astro/kyro-cms/admin/src/components';
|
|
5
|
+
const getFiles = (dir) => {
|
|
6
|
+
const dirents = fs.readdirSync(dir, { withFileTypes: true });
|
|
7
|
+
const files = dirents.map((dirent) => {
|
|
8
|
+
const res = path.resolve(dir, dirent.name);
|
|
9
|
+
return dirent.isDirectory() ? getFiles(res) : res;
|
|
10
|
+
});
|
|
11
|
+
return Array.prototype.concat(...files).filter(f => f.endsWith('.tsx'));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const files = getFiles(dir);
|
|
15
|
+
|
|
16
|
+
const replacements = [
|
|
17
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M12 4v16m8-8H4"[^>]*\/>\s*<\/svg>/g, replace: '<Plus className="w-4 h-4" />', icon: 'Plus' },
|
|
18
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"[^>]*\/>\s*<\/svg>/g, replace: '<Upload className="w-4 h-4" />', icon: 'Upload' },
|
|
19
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"[^>]*\/>\s*<\/svg>/g, replace: '<Copy className="w-4 h-4" />', icon: 'Copy' },
|
|
20
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M19 7l-\.867 12\.142A2 2 0 0116\.138 21H7\.862a2 2 0 01-1\.995-1\.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"[^>]*\/>\s*<\/svg>/g, replace: '<Trash2 className="w-4 h-4" />', icon: 'Trash2' },
|
|
21
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"[^>]*\/>\s*<\/svg>/g, replace: '<Lock className="w-4 h-4" />', icon: 'Lock' },
|
|
22
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M10 18a8 8 0 100-16 8 8 0 000 16zm3\.707-9\.293a1 1 0 00-1\.414-1\.414L9 10\.586 7\.707 9\.293a1 1 0 00-1\.414 1\.414l2 2a1 1 0 001\.414 0l4-4z"[^>]*\/>\s*<\/svg>/g, replace: '<CheckCircle2 className="w-4 h-4" />', icon: 'CheckCircle2' },
|
|
23
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1\.414-9\.414a2 2 0 112\.828 2\.828L11\.828 15H9v-2\.828l8\.586-8\.586z"[^>]*\/>\s*<\/svg>/g, replace: '<Edit2 className="w-4 h-4" />', icon: 'Edit2' },
|
|
24
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M10 18a8 8 0 100-16 8 8 0 000 16zM8\.707 7\.293a1 1 0 00-1\.414 1\.414L8\.586 10l-1\.293 1\.293a1 1 0 101\.414 1\.414L10 11\.414l1\.293 1\.293a1 1 0 001\.414-1\.414L11\.414 10l1\.293-1\.293a1 1 0 00-1\.414-1\.414L10 8\.586 8\.707 7\.293z"[^>]*\/>\s*<\/svg>/g, replace: '<XCircle className="w-4 h-4" />', icon: 'XCircle' },
|
|
25
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M6 18L18 6M6 6l12 12"[^>]*\/>\s*<\/svg>/g, replace: '<X className="w-4 h-4" />', icon: 'X' },
|
|
26
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"[^>]*\/>\s*<\/svg>/g, replace: '<Search className="w-4 h-4" />', icon: 'Search' },
|
|
27
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M5 13l4 4L19 7"[^>]*\/>\s*<\/svg>/g, replace: '<Check className="w-4 h-4" />', icon: 'Check' },
|
|
28
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2\.586a1 1 0 01-\.293\.707l-6\.414 6\.414a1 1 0 00-\.293\.707V17l-4 4v-6\.586a1 1 0 00-\.293-\.707L3\.293 7\.293A1 1 0 013 6\.586V4z"[^>]*\/>\s*<\/svg>/g, replace: '<Filter className="w-4 h-4" />', icon: 'Filter' },
|
|
29
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"[^>]*\/>\s*<\/svg>/g, replace: '<Columns3 className="w-4 h-4" />', icon: 'Columns3' },
|
|
30
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M12 5v14M5 12h14"[^>]*\/>\s*<\/svg>/g, replace: '<Plus className="w-4 h-4" />', icon: 'Plus' },
|
|
31
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"[^>]*\/>\s*<\/svg>/g, replace: '<Archive className="w-4 h-4" />', icon: 'Archive' },
|
|
32
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M5 15l7-7 7 7"[^>]*\/>\s*<\/svg>/g, replace: '<ChevronUp className="w-4 h-4" />', icon: 'ChevronUp' },
|
|
33
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M15 19l-7-7 7-7"[^>]*\/>\s*<\/svg>/g, replace: '<ChevronRight className="w-4 h-4" />', icon: 'ChevronRight' },
|
|
34
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M20 6L9 17l-5-5"[^>]*\/>\s*<\/svg>/g, replace: '<Check className="w-4 h-4" />', icon: 'Check' },
|
|
35
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3"[^>]*\/>\s*<\/svg>/g, replace: '<ExternalLink className="w-4 h-4" />', icon: 'ExternalLink' },
|
|
36
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"[^>]*\/>\s*<\/svg>/g, replace: '<File className="w-4 h-4" />', icon: 'File' },
|
|
37
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"[^>]*\/>\s*<\/svg>/g, replace: '<Clipboard className="w-4 h-4" />', icon: 'Clipboard' },
|
|
38
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M18 6L6 18M6 6l12 12"[^>]*\/>\s*<\/svg>/g, replace: '<X className="w-4 h-4" />', icon: 'X' },
|
|
39
|
+
{ match: /<svg[^>]*>\s*<path[^>]*d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2H5a2 2 0 00-2 2v1m2 2a2 2 0 11-4 0 2 2 0 014 0zm2 2h\.008v\.008H5v-\.008z"[^>]*\/>\s*<\/svg>/g, replace: '<Server className="w-8 h-8 text-[var(--kyro-sidebar-text-active)]" />', icon: 'Server' }
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const results = {};
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
let content = fs.readFileSync(file, 'utf8');
|
|
46
|
+
let changed = false;
|
|
47
|
+
let addedIcons = new Set();
|
|
48
|
+
|
|
49
|
+
for (const r of replacements) {
|
|
50
|
+
if (content.match(r.match)) {
|
|
51
|
+
content = content.replace(r.match, r.replace);
|
|
52
|
+
addedIcons.add(r.icon);
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (changed) {
|
|
58
|
+
results[file] = Array.from(addedIcons);
|
|
59
|
+
fs.writeFileSync(file, content);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(JSON.stringify(results, null, 2));
|
|
@@ -4,12 +4,14 @@ interface DropdownProps {
|
|
|
4
4
|
trigger: ReactNode;
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
align?: "left" | "right";
|
|
7
|
+
direction?: "up" | "down";
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function Dropdown({
|
|
10
11
|
trigger,
|
|
11
12
|
children,
|
|
12
13
|
align = "right",
|
|
14
|
+
direction = "up",
|
|
13
15
|
}: DropdownProps) {
|
|
14
16
|
const [open, setOpen] = useState(false);
|
|
15
17
|
const ref = useRef<HTMLDivElement>(null);
|
|
@@ -34,8 +36,11 @@ export function Dropdown({
|
|
|
34
36
|
</div>
|
|
35
37
|
{open && (
|
|
36
38
|
<div
|
|
37
|
-
className={`absolute z-[100]
|
|
38
|
-
|
|
39
|
+
className={`absolute z-[100] min-w-[200px] py-2 bg-[var(--kyro-surface)] rounded-2xl shadow-2xl border border-[var(--kyro-border)] animate-in fade-in zoom-in-95 duration-100 ${
|
|
40
|
+
align === "right" ? "right-0" : "left-0"
|
|
41
|
+
} ${
|
|
42
|
+
direction === "down" ? "top-full mt-2" : "bottom-full mb-2"
|
|
43
|
+
}`}
|
|
39
44
|
onClick={() => setOpen(false)}
|
|
40
45
|
>
|
|
41
46
|
{children}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { X } from "./icons";
|
|
1
2
|
import React, { useEffect, type ReactNode } from "react";
|
|
2
3
|
import { createPortal } from "react-dom";
|
|
3
4
|
|
|
@@ -17,8 +18,8 @@ interface ModalProps {
|
|
|
17
18
|
title: string;
|
|
18
19
|
children: ReactNode;
|
|
19
20
|
footer?: ReactNode;
|
|
20
|
-
size?: "sm" | "md" | "lg";
|
|
21
|
-
variant?: "default" | "danger";
|
|
21
|
+
size?: "sm" | "md" | "lg" | "full";
|
|
22
|
+
variant?: "default" | "danger" | "lightbox";
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function Modal({
|
|
@@ -52,43 +53,39 @@ export function Modal({
|
|
|
52
53
|
sm: "max-w-sm",
|
|
53
54
|
md: "max-w-md",
|
|
54
55
|
lg: "max-w-lg",
|
|
56
|
+
full: "w-full h-full max-w-none rounded-none border-0",
|
|
55
57
|
};
|
|
56
58
|
|
|
59
|
+
const isLightbox = variant === "lightbox";
|
|
60
|
+
|
|
57
61
|
return createPortal(
|
|
58
62
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
|
|
59
63
|
<div
|
|
60
|
-
className=
|
|
64
|
+
className={`absolute inset-0 transition-all duration-500 ${isLightbox ? "bg-black/95 backdrop-blur-none" : "bg-[var(--kyro-black)]/40 backdrop-blur-md"}`}
|
|
61
65
|
onClick={onClose}
|
|
62
66
|
/>
|
|
63
67
|
<div
|
|
64
|
-
className={`relative
|
|
68
|
+
className={`relative ${sizeClasses[size]} ${isLightbox ? "bg-transparent text-white" : "bg-[var(--kyro-surface)]"} ${!isLightbox && size !== "full" ? "rounded-[var(--kyro-radius-lg)]" : ""} shadow-2xl animate-in fade-in zoom-in-95 duration-300 ${!isLightbox ? "border" : ""} ${variant === "danger"
|
|
65
69
|
? "border-red-500/30"
|
|
66
70
|
: "border-[var(--kyro-border)]"
|
|
67
|
-
} overflow-hidden`}
|
|
71
|
+
} flex flex-col overflow-hidden`}
|
|
68
72
|
>
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<svg
|
|
79
|
-
width="20"
|
|
80
|
-
height="20"
|
|
81
|
-
viewBox="0 0 24 24"
|
|
82
|
-
fill="none"
|
|
83
|
-
stroke="currentColor"
|
|
84
|
-
strokeWidth="2.5"
|
|
73
|
+
{!isLightbox && (
|
|
74
|
+
<div className="flex items-center justify-between px-8 py-6 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50 backdrop-blur-md">
|
|
75
|
+
<h2 className="text-xl font-bold text-[var(--kyro-text-primary)]">
|
|
76
|
+
{title}
|
|
77
|
+
</h2>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={onClose}
|
|
81
|
+
className="p-2 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-xl hover:bg-[var(--kyro-surface)] transition-all duration-200"
|
|
85
82
|
>
|
|
86
|
-
<
|
|
87
|
-
</
|
|
88
|
-
</
|
|
89
|
-
|
|
90
|
-
<div className="px-8 py-8">{children}</div>
|
|
91
|
-
{footer && (
|
|
83
|
+
<X className="w-4 h-4" />
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<div className={`flex-1 overflow-auto ${isLightbox ? "" : "px-8 py-8"}`}>{children}</div>
|
|
88
|
+
{footer && !isLightbox && (
|
|
92
89
|
<div className="flex items-center justify-end gap-3 px-8 py-6 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)]/50">
|
|
93
90
|
{footer}
|
|
94
91
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { X } from "./icons";
|
|
1
2
|
import React, { useState } from "react";
|
|
2
3
|
import { createPortal } from "react-dom";
|
|
3
4
|
|
|
@@ -47,16 +48,7 @@ export function PromptModal({
|
|
|
47
48
|
onClick={onClose}
|
|
48
49
|
className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
49
50
|
>
|
|
50
|
-
<
|
|
51
|
-
width="20"
|
|
52
|
-
height="20"
|
|
53
|
-
viewBox="0 0 24 24"
|
|
54
|
-
fill="none"
|
|
55
|
-
stroke="currentColor"
|
|
56
|
-
strokeWidth="2"
|
|
57
|
-
>
|
|
58
|
-
<path d="M18 6L6 18M6 6l12 12" />
|
|
59
|
-
</svg>
|
|
51
|
+
<X className="w-4 h-4" />
|
|
60
52
|
</button>
|
|
61
53
|
</div>
|
|
62
54
|
<form onSubmit={handleSubmit}>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { X } from "./icons";
|
|
1
2
|
import React, { useEffect, useRef, useState, type ReactNode } from "react";
|
|
2
3
|
import { createPortal } from "react-dom";
|
|
3
4
|
|
|
@@ -78,16 +79,7 @@ export function SlidePanel({
|
|
|
78
79
|
onClick={onClose}
|
|
79
80
|
className="p-1.5 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-colors"
|
|
80
81
|
>
|
|
81
|
-
<
|
|
82
|
-
width="16"
|
|
83
|
-
height="16"
|
|
84
|
-
viewBox="0 0 24 24"
|
|
85
|
-
fill="none"
|
|
86
|
-
stroke="currentColor"
|
|
87
|
-
strokeWidth="2"
|
|
88
|
-
>
|
|
89
|
-
<path d="M18 6L6 18M6 6l12 12" />
|
|
90
|
-
</svg>
|
|
82
|
+
<X className="w-4 h-4" />
|
|
91
83
|
</button>
|
|
92
84
|
</div>
|
|
93
85
|
<div className="flex-1 overflow-y-auto p-4">{children}</div>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { Dropdown } from "./Dropdown";
|
|
4
|
+
import { Spinner } from "./Spinner";
|
|
5
|
+
|
|
6
|
+
export type SplitButtonStatus = "draft" | "published" | "scheduled" | "archived";
|
|
7
|
+
export type SplitButtonSaveStatus = "idle" | "saving" | "saved" | "error";
|
|
8
|
+
|
|
9
|
+
export interface SplitButtonProps {
|
|
10
|
+
/** Current publish status of the document */
|
|
11
|
+
status: SplitButtonStatus;
|
|
12
|
+
/** Live save operation status driven by the parent */
|
|
13
|
+
saveStatus: SplitButtonSaveStatus;
|
|
14
|
+
/** True when the form has unsaved changes vs last persisted version */
|
|
15
|
+
hasChanges: boolean;
|
|
16
|
+
/** Called when the main publish button is clicked */
|
|
17
|
+
onPublish: () => void;
|
|
18
|
+
/** Publish-variant items for the chevron dropdown (Save as Draft, Schedule) */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
direction?: "up" | "down";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function SplitButton({
|
|
25
|
+
status,
|
|
26
|
+
saveStatus,
|
|
27
|
+
hasChanges,
|
|
28
|
+
onPublish,
|
|
29
|
+
children,
|
|
30
|
+
disabled,
|
|
31
|
+
direction = "down",
|
|
32
|
+
}: SplitButtonProps) {
|
|
33
|
+
const isPublishedIdle =
|
|
34
|
+
status === "published" && !hasChanges && saveStatus !== "saving" && saveStatus !== "error";
|
|
35
|
+
|
|
36
|
+
const isDisabled = disabled || saveStatus === "saving" || isPublishedIdle;
|
|
37
|
+
|
|
38
|
+
// ── button colour ──────────────────────────────────────────────────────────
|
|
39
|
+
const btnBase =
|
|
40
|
+
"kyro-btn kyro-btn-sm text-[11px] font-regular tracking-widest transition-all duration-300 rounded-lg";
|
|
41
|
+
|
|
42
|
+
const getBtnClass = () => {
|
|
43
|
+
if (saveStatus === "saving") return `${btnBase} bg-[var(--kyro-primary)]/70 border-[var(--kyro-primary)]/70 text-[var(--kyro-sidebar-text-active)] cursor-wait`;
|
|
44
|
+
if (saveStatus === "saved") return `${btnBase} bg-[var(--kyro-success)] border-[var(--kyro-success)] text-[var(--kyro-sidebar-text-active)]`;
|
|
45
|
+
if (saveStatus === "error") return `${btnBase} bg-[var(--kyro-error)] border-[var(--kyro-error)] text-[var(--kyro-sidebar-text-active)]`;
|
|
46
|
+
if (isPublishedIdle) return `${btnBase} bg-[var(--kyro-gray-200)] border-[var(--kyro-gray-200)] text-[var(--kyro-text-muted)] cursor-not-allowed`;
|
|
47
|
+
// has changes → accent
|
|
48
|
+
return `${btnBase} bg-[var(--kyro-primary)] border-[var(--kyro-primary)] hover:bg-[var(--kyro-primary-hover)]`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const chevronBase =
|
|
52
|
+
"kyro-btn kyro-btn-md px-2 rounded-l-none border-l-[1px] border-white/20 transition-all duration-300";
|
|
53
|
+
|
|
54
|
+
const getChevronClass = () => {
|
|
55
|
+
if (saveStatus === "saving") return `${chevronBase} bg-[var(--kyro-primary)]/70 text-[var(--kyro-sidebar-text-active)] border-[var(--kyro-primary)]/70`;
|
|
56
|
+
if (saveStatus === "saved") return `${chevronBase} bg-[var(--kyro-success)] text-[var(--kyro-sidebar-text-active)] border-[var(--kyro-success)]`;
|
|
57
|
+
if (saveStatus === "error") return `${chevronBase} bg-[var(--kyro-error)] text-[var(--kyro-sidebar-text-active)] border-[var(--kyro-error)]`;
|
|
58
|
+
if (isPublishedIdle) return `${chevronBase} bg-[var(--kyro-gray-200)] text-[var(--kyro-text-muted)] border-[var(--kyro-gray-200)]`;
|
|
59
|
+
return `${chevronBase} bg-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] border-[var(--kyro-primary)] hover:bg-[var(--kyro-primary-hover)]`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ── label ──────────────────────────────────────────────────────────────────
|
|
63
|
+
const getLabel = () => {
|
|
64
|
+
if (saveStatus === "saving") return "Publishing...";
|
|
65
|
+
if (saveStatus === "saved") return "Published ✓";
|
|
66
|
+
if (saveStatus === "error") return "Retry";
|
|
67
|
+
if (isPublishedIdle) return "Published";
|
|
68
|
+
return "Publish Changes";
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="inline-flex items-center">
|
|
73
|
+
{/* Main publish button */}
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={onPublish}
|
|
77
|
+
disabled={isDisabled}
|
|
78
|
+
className={`${getBtnClass()} ${!children ? "rounded-r-lg border-r border-[var(--kyro-border)]" : ""}`}
|
|
79
|
+
>
|
|
80
|
+
{saveStatus === "saving" && <Spinner size="sm" className="inline mr-1.5" />}
|
|
81
|
+
{isPublishedIdle && (
|
|
82
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="inline mr-1">
|
|
83
|
+
<polyline points="20 6 9 17 4 12" />
|
|
84
|
+
</svg>
|
|
85
|
+
)}
|
|
86
|
+
{getLabel()}
|
|
87
|
+
</button>
|
|
88
|
+
|
|
89
|
+
{/* Chevron → publish-variant actions only */}
|
|
90
|
+
{children && (
|
|
91
|
+
<Dropdown
|
|
92
|
+
trigger={
|
|
93
|
+
<button type="button" className={getChevronClass()} disabled={saveStatus === "saving"}>
|
|
94
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
95
|
+
<path d="m6 9 6 6 6-6" />
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
}
|
|
99
|
+
direction={direction}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</Dropdown>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
@@ -2,7 +2,6 @@ import { useToastStore } from "../../lib/stores";
|
|
|
2
2
|
import { Toast } from "./Toast";
|
|
3
3
|
|
|
4
4
|
export function Toaster() {
|
|
5
|
-
console.log('Toaster mounted');
|
|
6
5
|
const toasts = useToastStore((state) => state.toasts);
|
|
7
6
|
const removeToast = useToastStore((state) => state.removeToast);
|
|
8
7
|
|
|
@@ -110,3 +110,4 @@ export { Grid3X3 as IconGrid3X3 } from "lucide-react";
|
|
|
110
110
|
// Direct re-exports for files that still use original lucide-react names
|
|
111
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
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";
|
|
113
|
+
export { Server as Server, XCircle as XCircle, Clipboard as Clipboard, Upload as Upload } from "lucide-react";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Plus, Lock, CheckCircle2, Edit2, Trash2, XCircle, X } from "../ui/icons";
|
|
1
2
|
import React, { useState } from "react";
|
|
2
3
|
import { useUIStore } from "../../lib/stores";
|
|
3
4
|
|
|
@@ -144,19 +145,7 @@ export function UsersList({
|
|
|
144
145
|
href={`${adminPath}/users/new`}
|
|
145
146
|
className="mt-2 inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-lg font-bold text-sm shadow-md"
|
|
146
147
|
>
|
|
147
|
-
<
|
|
148
|
-
className="w-3.5 h-3.5"
|
|
149
|
-
fill="none"
|
|
150
|
-
stroke="currentColor"
|
|
151
|
-
viewBox="0 0 24 24"
|
|
152
|
-
>
|
|
153
|
-
<path
|
|
154
|
-
strokeLinecap="round"
|
|
155
|
-
strokeLinejoin="round"
|
|
156
|
-
strokeWidth="2.5"
|
|
157
|
-
d="M12 5v14M5 12h14"
|
|
158
|
-
/>
|
|
159
|
-
</svg>
|
|
148
|
+
<Plus className="w-4 h-4" />
|
|
160
149
|
Add User
|
|
161
150
|
</a>
|
|
162
151
|
</div>
|
|
@@ -214,32 +203,12 @@ export function UsersList({
|
|
|
214
203
|
<td className="px-6 py-5">
|
|
215
204
|
{user.locked ? (
|
|
216
205
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-red-50 text-red-600">
|
|
217
|
-
<
|
|
218
|
-
className="w-3 h-3"
|
|
219
|
-
fill="currentColor"
|
|
220
|
-
viewBox="0 0 20 20"
|
|
221
|
-
>
|
|
222
|
-
<path
|
|
223
|
-
fillRule="evenodd"
|
|
224
|
-
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
|
|
225
|
-
clipRule="evenodd"
|
|
226
|
-
/>
|
|
227
|
-
</svg>
|
|
206
|
+
<Lock className="w-4 h-4" />
|
|
228
207
|
Locked
|
|
229
208
|
</span>
|
|
230
209
|
) : (
|
|
231
210
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold bg-green-50 text-green-600">
|
|
232
|
-
<
|
|
233
|
-
className="w-3 h-3"
|
|
234
|
-
fill="currentColor"
|
|
235
|
-
viewBox="0 0 20 20"
|
|
236
|
-
>
|
|
237
|
-
<path
|
|
238
|
-
fillRule="evenodd"
|
|
239
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
240
|
-
clipRule="evenodd"
|
|
241
|
-
/>
|
|
242
|
-
</svg>
|
|
211
|
+
<CheckCircle2 className="w-4 h-4" />
|
|
243
212
|
Active
|
|
244
213
|
</span>
|
|
245
214
|
)}
|
|
@@ -254,19 +223,7 @@ export function UsersList({
|
|
|
254
223
|
className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-gray-100 hover:text-[#0b1222] transition-colors"
|
|
255
224
|
onClick={(e) => e.stopPropagation()}
|
|
256
225
|
>
|
|
257
|
-
<
|
|
258
|
-
className="w-4 h-4"
|
|
259
|
-
fill="none"
|
|
260
|
-
stroke="currentColor"
|
|
261
|
-
viewBox="0 0 24 24"
|
|
262
|
-
>
|
|
263
|
-
<path
|
|
264
|
-
strokeLinecap="round"
|
|
265
|
-
strokeLinejoin="round"
|
|
266
|
-
strokeWidth="2"
|
|
267
|
-
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
268
|
-
/>
|
|
269
|
-
</svg>
|
|
226
|
+
<Edit2 className="w-4 h-4" />
|
|
270
227
|
</a>
|
|
271
228
|
<button
|
|
272
229
|
onClick={(e) => {
|
|
@@ -275,19 +232,7 @@ export function UsersList({
|
|
|
275
232
|
}}
|
|
276
233
|
className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[#64748b] hover:bg-red-50 hover:text-red-600 transition-colors"
|
|
277
234
|
>
|
|
278
|
-
<
|
|
279
|
-
className="w-4 h-4"
|
|
280
|
-
fill="none"
|
|
281
|
-
stroke="currentColor"
|
|
282
|
-
viewBox="0 0 24 24"
|
|
283
|
-
>
|
|
284
|
-
<path
|
|
285
|
-
strokeLinecap="round"
|
|
286
|
-
strokeLinejoin="round"
|
|
287
|
-
strokeWidth="2"
|
|
288
|
-
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
289
|
-
/>
|
|
290
|
-
</svg>
|
|
235
|
+
<Trash2 className="w-4 h-4" />
|
|
291
236
|
</button>
|
|
292
237
|
</div>
|
|
293
238
|
</td>
|
|
@@ -300,35 +245,13 @@ export function UsersList({
|
|
|
300
245
|
|
|
301
246
|
{errorMsg && (
|
|
302
247
|
<div className="surface-tile p-4 flex items-center gap-3 border border-red-200">
|
|
303
|
-
<
|
|
304
|
-
className="w-5 h-5 text-red-500 flex-shrink-0"
|
|
305
|
-
fill="currentColor"
|
|
306
|
-
viewBox="0 0 20 20"
|
|
307
|
-
>
|
|
308
|
-
<path
|
|
309
|
-
fillRule="evenodd"
|
|
310
|
-
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
311
|
-
clipRule="evenodd"
|
|
312
|
-
/>
|
|
313
|
-
</svg>
|
|
248
|
+
<XCircle className="w-4 h-4" />
|
|
314
249
|
<p className="text-sm font-medium text-red-500">{errorMsg}</p>
|
|
315
250
|
<button
|
|
316
251
|
onClick={() => setErrorMsg(null)}
|
|
317
252
|
className="ml-auto text-red-400 hover:text-red-600"
|
|
318
253
|
>
|
|
319
|
-
<
|
|
320
|
-
className="w-4 h-4"
|
|
321
|
-
fill="none"
|
|
322
|
-
stroke="currentColor"
|
|
323
|
-
viewBox="0 0 24 24"
|
|
324
|
-
>
|
|
325
|
-
<path
|
|
326
|
-
strokeLinecap="round"
|
|
327
|
-
strokeLinejoin="round"
|
|
328
|
-
strokeWidth="2"
|
|
329
|
-
d="M6 18L18 6M6 6l12 12"
|
|
330
|
-
/>
|
|
331
|
-
</svg>
|
|
254
|
+
<X className="w-4 h-4" />
|
|
332
255
|
</button>
|
|
333
256
|
</div>
|
|
334
257
|
)}
|