tetrons 2.2.2 → 2.2.4
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/app/api/register/route.d.ts +6 -0
- package/dist/app/api/register/route.js +26 -0
- package/dist/app/api/validate/route.d.ts +7 -0
- package/dist/app/api/validate/route.js +18 -0
- package/dist/app/page.jsx +47 -1
- package/dist/components/tetrons/EditorContent.d.ts +5 -1
- package/dist/components/tetrons/EditorContent.jsx +36 -1
- package/dist/components/tetrons/toolbar/ToolbarButton.jsx +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +40 -6
- package/dist/lib/db.d.ts +1 -0
- package/dist/lib/db.js +15 -0
- package/dist/models/ApiKey.d.ts +2 -0
- package/dist/models/ApiKey.js +14 -0
- package/dist/styles/styles/tetrons.css +371 -0
- package/dist/styles/tetrons.css +42 -0
- package/dist/utils/apiKeyUtils.d.ts +11 -0
- package/dist/utils/apiKeyUtils.js +17 -0
- package/package.json +6 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { connectDB } from "../../../lib/db";
|
|
3
|
+
import { ApiKey } from "../../../models/ApiKey";
|
|
4
|
+
import { generateApiKey } from "../../../utils/apiKeyUtils";
|
|
5
|
+
export async function POST(req) {
|
|
6
|
+
const { email, organization, version } = await req.json();
|
|
7
|
+
if (!email || !organization || !version)
|
|
8
|
+
return NextResponse.json({ error: "Missing fields" }, { status: 400 });
|
|
9
|
+
await connectDB();
|
|
10
|
+
if (version === "free") {
|
|
11
|
+
const expiresAt = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
|
|
12
|
+
const apiKey = generateApiKey(48);
|
|
13
|
+
await ApiKey.deleteMany({ email, version });
|
|
14
|
+
await ApiKey.create({
|
|
15
|
+
email,
|
|
16
|
+
organization,
|
|
17
|
+
version,
|
|
18
|
+
apiKey,
|
|
19
|
+
expiresAt,
|
|
20
|
+
});
|
|
21
|
+
return NextResponse.json({ apiKey, expiresAt });
|
|
22
|
+
}
|
|
23
|
+
const apiKey = generateApiKey(48);
|
|
24
|
+
await ApiKey.create({ email, organization, version, apiKey });
|
|
25
|
+
return NextResponse.json({ apiKey });
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { connectDB } from "../../../lib/db";
|
|
3
|
+
import { ApiKey } from "../../../models/ApiKey";
|
|
4
|
+
export async function POST(req) {
|
|
5
|
+
const { apiKey } = await req.json();
|
|
6
|
+
if (!apiKey)
|
|
7
|
+
return NextResponse.json({ error: "API key required" }, { status: 400 });
|
|
8
|
+
await connectDB();
|
|
9
|
+
const keyEntry = await ApiKey.findOne({ apiKey });
|
|
10
|
+
if (!keyEntry)
|
|
11
|
+
return NextResponse.json({ error: "Invalid key" }, { status: 401 });
|
|
12
|
+
if (keyEntry.version === "free" &&
|
|
13
|
+
keyEntry.expiresAt &&
|
|
14
|
+
new Date() > new Date(keyEntry.expiresAt)) {
|
|
15
|
+
return NextResponse.json({ error: "Free trial expired" }, { status: 403 });
|
|
16
|
+
}
|
|
17
|
+
return NextResponse.json({ valid: true, version: keyEntry.version });
|
|
18
|
+
}
|
package/dist/app/page.jsx
CHANGED
|
@@ -1,9 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
1
3
|
import EditorContent from "../components/tetrons/EditorContent";
|
|
2
4
|
import "../styles/tetrons.css";
|
|
3
5
|
export default function Home() {
|
|
6
|
+
const [apiKey, setApiKey] = useState(null);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const fetchApiKey = async () => {
|
|
10
|
+
try {
|
|
11
|
+
const cachedKey = localStorage.getItem("my-api-key");
|
|
12
|
+
if (cachedKey) {
|
|
13
|
+
setApiKey(cachedKey);
|
|
14
|
+
setLoading(false);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const res = await fetch("/api/register", {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
},
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
email: "mr.swastikjha@gmail.com",
|
|
24
|
+
organization: "FCSPL",
|
|
25
|
+
version: "free",
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
if (!res.ok || !data.apiKey) {
|
|
30
|
+
throw new Error(data.error || "Failed to fetch API key");
|
|
31
|
+
}
|
|
32
|
+
localStorage.setItem("my-api-key", data.apiKey);
|
|
33
|
+
setApiKey(data.apiKey);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error("Error fetching API key:", error);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
setLoading(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
fetchApiKey();
|
|
43
|
+
}, []);
|
|
44
|
+
if (loading) {
|
|
45
|
+
return <div className="text-center p-4">⏳ Loading Editor...</div>;
|
|
46
|
+
}
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
return (<div className="text-center text-red-600">❌ Failed to load API key</div>);
|
|
49
|
+
}
|
|
4
50
|
return (<main className="flex flex-col h-screen overflow-hidden">
|
|
5
51
|
<div className="flex-1 overflow-auto flex flex-col">
|
|
6
|
-
<EditorContent />
|
|
52
|
+
<EditorContent apiKey={apiKey}/>
|
|
7
53
|
</div>
|
|
8
54
|
</main>);
|
|
9
55
|
}
|
|
@@ -45,9 +45,38 @@ import ts from "highlight.js/lib/languages/typescript";
|
|
|
45
45
|
const lowlight = createLowlight();
|
|
46
46
|
lowlight.register("js", js);
|
|
47
47
|
lowlight.register("ts", ts);
|
|
48
|
-
export default function EditorContent() {
|
|
48
|
+
export default function EditorContent({ apiKey }) {
|
|
49
|
+
const [isValid, setIsValid] = React.useState(null);
|
|
50
|
+
const [error, setError] = React.useState(null);
|
|
49
51
|
const [versions, setVersions] = React.useState([]);
|
|
50
52
|
const [currentVersionIndex, setCurrentVersionIndex] = React.useState(null);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const validateKey = async () => {
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch("/api/validate", {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({ apiKey }),
|
|
62
|
+
});
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
if (!res.ok)
|
|
65
|
+
throw new Error(data.error || "Invalid API Key");
|
|
66
|
+
setIsValid(true);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
if (err instanceof Error) {
|
|
70
|
+
setError(err.message || "Invalid API Key");
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
setError("Invalid API Key");
|
|
74
|
+
}
|
|
75
|
+
setIsValid(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
validateKey();
|
|
79
|
+
}, [apiKey]);
|
|
51
80
|
const editor = useEditor({
|
|
52
81
|
extensions: [
|
|
53
82
|
Document,
|
|
@@ -133,6 +162,12 @@ export default function EditorContent() {
|
|
|
133
162
|
setCurrentVersionIndex(index);
|
|
134
163
|
}
|
|
135
164
|
};
|
|
165
|
+
if (isValid === false) {
|
|
166
|
+
return <div className="editor-error">⚠️ {error}</div>;
|
|
167
|
+
}
|
|
168
|
+
if (isValid === null) {
|
|
169
|
+
return <div className="editor-loading">🔍 Validating license...</div>;
|
|
170
|
+
}
|
|
136
171
|
return (<div className="editor-container">
|
|
137
172
|
<div className="editor-toolbar">
|
|
138
173
|
<button type="button" onClick={saveVersion} disabled={!editor} className="editor-save-btn">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
const ToolbarButton = React.forwardRef(({ icon: Icon, onClick, disabled = false, title, label, isActive = false }, ref) => {
|
|
3
|
-
return (<button type="button" ref={ref} onClick={onClick} disabled={disabled} title={title !== null && title !== void 0 ? title : label} aria-label={title !== null && title !== void 0 ? title : label} className={`
|
|
3
|
+
return (<button type="button" ref={ref} onClick={onClick} disabled={disabled} title={title !== null && title !== void 0 ? title : label} aria-label={title !== null && title !== void 0 ? title : label} className={`toolbar-button ${isActive ? "active" : ""}`}>
|
|
4
4
|
<Icon size={20}/>
|
|
5
5
|
</button>);
|
|
6
6
|
});
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type EditorContentProps = {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
};
|
|
6
|
+
declare function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
|
|
4
7
|
|
|
5
8
|
export { EditorContent, EditorContent as default };
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -14370,7 +14370,7 @@ var ToolbarButton = React.forwardRef(
|
|
|
14370
14370
|
disabled,
|
|
14371
14371
|
title: title ?? label,
|
|
14372
14372
|
"aria-label": title ?? label,
|
|
14373
|
-
className: `
|
|
14373
|
+
className: `toolbar-button ${isActive ? "active" : ""}`
|
|
14374
14374
|
},
|
|
14375
14375
|
/* @__PURE__ */ React.createElement(Icon, { size: 20 })
|
|
14376
14376
|
);
|
|
@@ -14403,9 +14403,10 @@ function ActionGroup({ editor }) {
|
|
|
14403
14403
|
".ProseMirror"
|
|
14404
14404
|
);
|
|
14405
14405
|
if (element) {
|
|
14406
|
-
const
|
|
14406
|
+
const style = element.style;
|
|
14407
|
+
const currentZoom = parseFloat(style.zoom || "1");
|
|
14407
14408
|
const next = Math.min(currentZoom + 0.1, 2);
|
|
14408
|
-
|
|
14409
|
+
style.zoom = next.toString();
|
|
14409
14410
|
}
|
|
14410
14411
|
};
|
|
14411
14412
|
const zoomOut = () => {
|
|
@@ -14413,9 +14414,10 @@ function ActionGroup({ editor }) {
|
|
|
14413
14414
|
".ProseMirror"
|
|
14414
14415
|
);
|
|
14415
14416
|
if (element) {
|
|
14416
|
-
const
|
|
14417
|
+
const style = element.style;
|
|
14418
|
+
const currentZoom = parseFloat(style.zoom || "1");
|
|
14417
14419
|
const next = Math.max(currentZoom - 0.1, 0.5);
|
|
14418
|
-
|
|
14420
|
+
style.zoom = next.toString();
|
|
14419
14421
|
}
|
|
14420
14422
|
};
|
|
14421
14423
|
const handlePrint = () => {
|
|
@@ -17066,9 +17068,35 @@ function typescript(hljs) {
|
|
|
17066
17068
|
var lowlight = createLowlight();
|
|
17067
17069
|
lowlight.register("js", javascript);
|
|
17068
17070
|
lowlight.register("ts", typescript);
|
|
17069
|
-
function EditorContent() {
|
|
17071
|
+
function EditorContent({ apiKey }) {
|
|
17072
|
+
const [isValid, setIsValid] = React13.useState(null);
|
|
17073
|
+
const [error, setError] = React13.useState(null);
|
|
17070
17074
|
const [versions, setVersions] = React13.useState([]);
|
|
17071
17075
|
const [currentVersionIndex, setCurrentVersionIndex] = React13.useState(null);
|
|
17076
|
+
useEffect7(() => {
|
|
17077
|
+
const validateKey = async () => {
|
|
17078
|
+
try {
|
|
17079
|
+
const res = await fetch("/api/validate", {
|
|
17080
|
+
method: "POST",
|
|
17081
|
+
headers: {
|
|
17082
|
+
"Content-Type": "application/json"
|
|
17083
|
+
},
|
|
17084
|
+
body: JSON.stringify({ apiKey })
|
|
17085
|
+
});
|
|
17086
|
+
const data = await res.json();
|
|
17087
|
+
if (!res.ok) throw new Error(data.error || "Invalid API Key");
|
|
17088
|
+
setIsValid(true);
|
|
17089
|
+
} catch (err) {
|
|
17090
|
+
if (err instanceof Error) {
|
|
17091
|
+
setError(err.message || "Invalid API Key");
|
|
17092
|
+
} else {
|
|
17093
|
+
setError("Invalid API Key");
|
|
17094
|
+
}
|
|
17095
|
+
setIsValid(false);
|
|
17096
|
+
}
|
|
17097
|
+
};
|
|
17098
|
+
validateKey();
|
|
17099
|
+
}, [apiKey]);
|
|
17072
17100
|
const editor = useEditor({
|
|
17073
17101
|
extensions: [
|
|
17074
17102
|
Document,
|
|
@@ -17152,6 +17180,12 @@ function EditorContent() {
|
|
|
17152
17180
|
setCurrentVersionIndex(index);
|
|
17153
17181
|
}
|
|
17154
17182
|
};
|
|
17183
|
+
if (isValid === false) {
|
|
17184
|
+
return /* @__PURE__ */ React13.createElement("div", { className: "editor-error" }, "\u26A0\uFE0F ", error);
|
|
17185
|
+
}
|
|
17186
|
+
if (isValid === null) {
|
|
17187
|
+
return /* @__PURE__ */ React13.createElement("div", { className: "editor-loading" }, "\u{1F50D} Validating license...");
|
|
17188
|
+
}
|
|
17155
17189
|
return /* @__PURE__ */ React13.createElement("div", { className: "editor-container" }, /* @__PURE__ */ React13.createElement("div", { className: "editor-toolbar" }, /* @__PURE__ */ React13.createElement(
|
|
17156
17190
|
"button",
|
|
17157
17191
|
{
|
package/dist/lib/db.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function connectDB(): Promise<void>;
|
package/dist/lib/db.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
const MONGO_URL = process.env.MONGO_URL;
|
|
3
|
+
export const connectDB = async () => {
|
|
4
|
+
if (mongoose.connection.readyState >= 1) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
await mongoose.connect(MONGO_URL);
|
|
9
|
+
console.log("Connected to MongoDB 👍");
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
console.error("MongoDB connection failed ❌", error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
const ApiKeySchema = new mongoose.Schema({
|
|
3
|
+
email: { type: String, required: true },
|
|
4
|
+
organization: { type: String, required: true },
|
|
5
|
+
version: {
|
|
6
|
+
type: String,
|
|
7
|
+
enum: ["free", "pro", "premium", "platinum"],
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
apiKey: { type: String, required: true, unique: true },
|
|
11
|
+
createdAt: { type: Date, default: Date.now },
|
|
12
|
+
expiresAt: { type: Date },
|
|
13
|
+
});
|
|
14
|
+
export const ApiKey = mongoose.models.ApiKey || mongoose.model("ApiKey", ApiKeySchema);
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
.editor-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.editor-toolbar {
|
|
8
|
+
padding: 0.5rem;
|
|
9
|
+
border-bottom: 1px solid #d1d5db;
|
|
10
|
+
background-color: #f9fafb;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-wrap: wrap;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 0.75rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.editor-save-btn {
|
|
18
|
+
padding: 0.25rem 0.75rem;
|
|
19
|
+
background-color: #2563eb;
|
|
20
|
+
color: white;
|
|
21
|
+
border-radius: 0.375rem;
|
|
22
|
+
transition: background-color 0.2s;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.editor-save-btn:hover {
|
|
26
|
+
background-color: #1d4ed8;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.editor-save-btn:disabled {
|
|
30
|
+
opacity: 0.5;
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.editor-version-btn {
|
|
35
|
+
padding: 0.25rem 0.5rem;
|
|
36
|
+
border: 1px solid #d1d5db;
|
|
37
|
+
color: #374151;
|
|
38
|
+
border-radius: 0.375rem;
|
|
39
|
+
font-size: 0.875rem;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
transition: all 0.2s;
|
|
42
|
+
background: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.editor-version-btn:hover {
|
|
46
|
+
border-color: #4b5563;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.editor-version-btn.active {
|
|
50
|
+
border-color: #2563eb;
|
|
51
|
+
color: #2563eb;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.editor-content-wrapper {
|
|
56
|
+
flex-grow: 1;
|
|
57
|
+
padding: 1.5rem;
|
|
58
|
+
background-color: white;
|
|
59
|
+
border: 1px solid #d1d5db;
|
|
60
|
+
border-radius: 0.5rem;
|
|
61
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
62
|
+
overflow: auto;
|
|
63
|
+
position: relative;
|
|
64
|
+
min-height: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.editor-loading {
|
|
68
|
+
color: #6b7280;
|
|
69
|
+
font-size: 0.875rem;
|
|
70
|
+
text-align: center;
|
|
71
|
+
padding: 1rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.editor-content-wrapper .ProseMirror {
|
|
75
|
+
outline: none;
|
|
76
|
+
min-height: 300px;
|
|
77
|
+
font-size: 1rem;
|
|
78
|
+
line-height: 1.75;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.editor-content-wrapper .ProseMirror[data-placeholder]:empty::before {
|
|
82
|
+
content: attr(data-placeholder);
|
|
83
|
+
color: #9ca3af;
|
|
84
|
+
float: left;
|
|
85
|
+
height: 0;
|
|
86
|
+
pointer-events: none;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ProseMirror pre {
|
|
90
|
+
background: #f3f4f6;
|
|
91
|
+
padding: 1rem;
|
|
92
|
+
border-radius: 0.375rem;
|
|
93
|
+
font-family: monospace;
|
|
94
|
+
font-size: 0.875rem;
|
|
95
|
+
overflow-x: auto;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.editor-versions-wrapper {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 0.5rem;
|
|
102
|
+
overflow-x: auto;
|
|
103
|
+
max-width: 100%;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.editor-no-versions {
|
|
107
|
+
color: #6b7280;
|
|
108
|
+
font-size: 0.875rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.tetrons-toolbar {
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-wrap: wrap;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 1rem;
|
|
116
|
+
padding: 0.75rem;
|
|
117
|
+
border-bottom: 1px solid #e5e7eb;
|
|
118
|
+
background-color: white;
|
|
119
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
120
|
+
position: relative;
|
|
121
|
+
z-index: 10;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.tetrons-toolbar .group {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 0.5rem;
|
|
128
|
+
border-right: 1px solid #e5e7eb;
|
|
129
|
+
padding-right: 0.75rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.tetrons-toolbar input[type="checkbox"] {
|
|
133
|
+
width: 1rem;
|
|
134
|
+
height: 1rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.tetrons-toolbar label {
|
|
138
|
+
font-size: 0.875rem;
|
|
139
|
+
user-select: none;
|
|
140
|
+
-webkit-user-select: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.misc-group {
|
|
144
|
+
display: flex;
|
|
145
|
+
gap: 0.25rem;
|
|
146
|
+
align-items: center;
|
|
147
|
+
border-right: 1px solid #e5e7eb;
|
|
148
|
+
padding-right: 0.75rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.list-align-group {
|
|
152
|
+
display: flex;
|
|
153
|
+
gap: 0.25rem;
|
|
154
|
+
border-right: 1px solid #e5e7eb;
|
|
155
|
+
padding-right: 0.75rem;
|
|
156
|
+
align-items: center;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.insert-group {
|
|
160
|
+
display: flex;
|
|
161
|
+
gap: 0.25rem;
|
|
162
|
+
border-right: 1px solid #e5e7eb;
|
|
163
|
+
padding-right: 0.75rem;
|
|
164
|
+
position: relative;
|
|
165
|
+
align-items: center;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.table-grid-popup {
|
|
169
|
+
position: absolute;
|
|
170
|
+
top: 2.5rem;
|
|
171
|
+
left: 0;
|
|
172
|
+
background-color: white;
|
|
173
|
+
border: 1px solid #d1d5db;
|
|
174
|
+
border-radius: 0.25rem;
|
|
175
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
176
|
+
padding: 0.5rem;
|
|
177
|
+
z-index: 20;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.table-grid {
|
|
181
|
+
display: grid;
|
|
182
|
+
grid-template-columns: repeat(10, 1fr);
|
|
183
|
+
gap: 1px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.table-grid-cell {
|
|
187
|
+
width: 1.25rem;
|
|
188
|
+
height: 1.25rem;
|
|
189
|
+
border: 1px solid #d1d5db;
|
|
190
|
+
background-color: #f3f4f6;
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.table-grid-cell.selected {
|
|
195
|
+
background-color: #3b82f6;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.table-grid-label {
|
|
199
|
+
margin-top: 0.5rem;
|
|
200
|
+
font-size: 0.75rem;
|
|
201
|
+
text-align: center;
|
|
202
|
+
color: #6b7280;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.hidden-input {
|
|
206
|
+
display: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.emoji-picker {
|
|
210
|
+
position: absolute;
|
|
211
|
+
top: 2.5rem;
|
|
212
|
+
left: 0;
|
|
213
|
+
z-index: 50;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.font-style-group {
|
|
217
|
+
display: flex;
|
|
218
|
+
gap: 0.25rem;
|
|
219
|
+
border-right: 1px solid #e5e7eb;
|
|
220
|
+
padding-right: 0.75rem;
|
|
221
|
+
align-items: center;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.font-style-group select {
|
|
225
|
+
font-size: 0.875rem;
|
|
226
|
+
border: 1px solid #d1d5db;
|
|
227
|
+
border-radius: 0.25rem;
|
|
228
|
+
padding: 0.125rem 0.25rem;
|
|
229
|
+
margin-right: 0.5rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.color-label {
|
|
233
|
+
position: relative;
|
|
234
|
+
width: 2rem;
|
|
235
|
+
height: 2rem;
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
align-items: center;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.color-indicator {
|
|
243
|
+
content: "";
|
|
244
|
+
position: absolute;
|
|
245
|
+
bottom: 2px;
|
|
246
|
+
left: 50%;
|
|
247
|
+
transform: translateX(-50%);
|
|
248
|
+
width: 12px;
|
|
249
|
+
height: 4px;
|
|
250
|
+
background-color: var(--indicator-color, #000000);
|
|
251
|
+
border-radius: 2px;
|
|
252
|
+
pointer-events: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.color-label input[type="color"] {
|
|
256
|
+
position: absolute;
|
|
257
|
+
inset: 0;
|
|
258
|
+
opacity: 0;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.file-group {
|
|
263
|
+
display: flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
gap: 0.25rem;
|
|
266
|
+
border-right: 1px solid #e5e7eb;
|
|
267
|
+
padding-right: 0.75rem;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.file-group input[type="file"] {
|
|
271
|
+
display: none;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.clipboard-group {
|
|
275
|
+
display: flex;
|
|
276
|
+
gap: 0.25rem;
|
|
277
|
+
border-right: 1px solid #e5e7eb;
|
|
278
|
+
padding-right: 0.75rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.action-group {
|
|
282
|
+
position: relative;
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 0.25rem;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.export-button {
|
|
289
|
+
display: flex;
|
|
290
|
+
align-items: center;
|
|
291
|
+
gap: 0.25rem;
|
|
292
|
+
padding: 0.25rem 0.5rem;
|
|
293
|
+
border-radius: 0.25rem;
|
|
294
|
+
background: transparent;
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.export-button:hover {
|
|
299
|
+
background-color: #f3f4f6;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.export-button:focus {
|
|
303
|
+
outline: none;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.export-dropdown {
|
|
307
|
+
position: absolute;
|
|
308
|
+
z-index: 10;
|
|
309
|
+
margin-top: 0.5rem;
|
|
310
|
+
width: 10rem;
|
|
311
|
+
background-color: #fff;
|
|
312
|
+
border: 1px solid #e5e7eb;
|
|
313
|
+
border-radius: 0.25rem;
|
|
314
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.export-dropdown button {
|
|
318
|
+
width: 100%;
|
|
319
|
+
text-align: left;
|
|
320
|
+
padding: 0.5rem 1rem;
|
|
321
|
+
background: none;
|
|
322
|
+
border: none;
|
|
323
|
+
font-size: 0.875rem;
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.export-dropdown button:hover {
|
|
328
|
+
background-color: #f3f4f6;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.toolbar-button {
|
|
332
|
+
padding: 0.5rem;
|
|
333
|
+
border: none;
|
|
334
|
+
background-color: transparent;
|
|
335
|
+
border-radius: 0.375rem;
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
display: flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
justify-content: center;
|
|
340
|
+
transition: background-color 0.2s ease;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.toolbar-button:hover {
|
|
344
|
+
background-color: #e5e7eb;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.toolbar-button.active {
|
|
348
|
+
background-color: #d1d5db;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.toolbar-button:disabled {
|
|
352
|
+
opacity: 0.5;
|
|
353
|
+
cursor: not-allowed;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.tableWrapper {
|
|
357
|
+
overflow-x: auto;
|
|
358
|
+
margin: 1rem 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.tableWrapper table {
|
|
362
|
+
width: 100%;
|
|
363
|
+
border-collapse: collapse;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.tableWrapper th,
|
|
367
|
+
.tableWrapper td {
|
|
368
|
+
border: 1px solid #d1d5db;
|
|
369
|
+
padding: 0.5rem;
|
|
370
|
+
text-align: left;
|
|
371
|
+
}
|
package/dist/styles/tetrons.css
CHANGED
|
@@ -327,3 +327,45 @@
|
|
|
327
327
|
.export-dropdown button:hover {
|
|
328
328
|
background-color: #f3f4f6;
|
|
329
329
|
}
|
|
330
|
+
|
|
331
|
+
.toolbar-button {
|
|
332
|
+
padding: 0.5rem;
|
|
333
|
+
border: none;
|
|
334
|
+
background-color: transparent;
|
|
335
|
+
border-radius: 0.375rem;
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
display: flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
justify-content: center;
|
|
340
|
+
transition: background-color 0.2s ease;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.toolbar-button:hover {
|
|
344
|
+
background-color: #e5e7eb;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.toolbar-button.active {
|
|
348
|
+
background-color: #d1d5db;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.toolbar-button:disabled {
|
|
352
|
+
opacity: 0.5;
|
|
353
|
+
cursor: not-allowed;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.tableWrapper {
|
|
357
|
+
overflow-x: auto;
|
|
358
|
+
margin: 1rem 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.tableWrapper table {
|
|
362
|
+
width: 100%;
|
|
363
|
+
border-collapse: collapse;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.tableWrapper th,
|
|
367
|
+
.tableWrapper td {
|
|
368
|
+
border: 1px solid #d1d5db;
|
|
369
|
+
padding: 0.5rem;
|
|
370
|
+
text-align: left;
|
|
371
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a secure random API key
|
|
3
|
+
* @param length Length of the key (default 48 characters)
|
|
4
|
+
* @returns Hex string API key
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateApiKey(length?: number): string;
|
|
7
|
+
/**
|
|
8
|
+
* Generates a unique key with optional prefix (e.g., for versioning or branding)
|
|
9
|
+
* @param prefix Optional prefix like 'PRO-', 'PREMIUM-', etc.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateVersionedKey(prefix?: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Generates a secure random API key
|
|
4
|
+
* @param length Length of the key (default 48 characters)
|
|
5
|
+
* @returns Hex string API key
|
|
6
|
+
*/
|
|
7
|
+
export function generateApiKey(length = 48) {
|
|
8
|
+
return crypto.randomBytes(length / 2).toString("hex"); // length must be even
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generates a unique key with optional prefix (e.g., for versioning or branding)
|
|
12
|
+
* @param prefix Optional prefix like 'PRO-', 'PREMIUM-', etc.
|
|
13
|
+
*/
|
|
14
|
+
export function generateVersionedKey(prefix = "") {
|
|
15
|
+
const key = generateApiKey(48);
|
|
16
|
+
return `${prefix}${key}`;
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tetrons",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.4",
|
|
4
4
|
"description": "A Next.js project written in TypeScript",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "next dev --turbo",
|
|
9
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --out-dir dist",
|
|
9
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --out-dir dist && copyfiles -u 1 src/styles/*.css dist/styles",
|
|
10
10
|
"start": "next start",
|
|
11
11
|
"lint": "next lint"
|
|
12
12
|
},
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"dom-to-pdf": "^0.3.2",
|
|
30
30
|
"html2pdf.js": "^0.10.3",
|
|
31
31
|
"lowlight": "^3.3.0",
|
|
32
|
+
"mongoose": "^8.16.0",
|
|
32
33
|
"react-icons": "^5.5.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"@types/node": "^20",
|
|
43
44
|
"@types/react": "^19",
|
|
44
45
|
"@types/react-dom": "^19",
|
|
46
|
+
"copyfiles": "^2.4.1",
|
|
45
47
|
"eslint": "^9",
|
|
46
48
|
"eslint-config-next": "^15.3.2",
|
|
47
49
|
"tailwindcss": "^4",
|
|
@@ -65,7 +67,8 @@
|
|
|
65
67
|
"import": "./dist/index.js",
|
|
66
68
|
"require": "./dist/index.js",
|
|
67
69
|
"types": "./dist/index.d.ts"
|
|
68
|
-
}
|
|
70
|
+
},
|
|
71
|
+
"./style.css": "./dist/styles/tetrons.css"
|
|
69
72
|
},
|
|
70
73
|
"files": [
|
|
71
74
|
"dist",
|