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.
@@ -0,0 +1,6 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function POST(req: NextRequest): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ apiKey: string;
6
+ }>>;
@@ -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,7 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function POST(req: NextRequest): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ valid: boolean;
6
+ version: any;
7
+ }>>;
@@ -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
  }
@@ -1,2 +1,6 @@
1
1
  import React from "react";
2
- export default function EditorContent(): React.JSX.Element;
2
+ type EditorContentProps = {
3
+ apiKey: string;
4
+ };
5
+ export default function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
6
+ export {};
@@ -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={`p-2 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${isActive ? "bg-gray-300" : ""}`}>
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
- declare function EditorContent(): React.JSX.Element;
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
@@ -1,4 +1,3 @@
1
1
  export { default as EditorContent } from "./components/tetrons/EditorContent";
2
- import "./styles/tetrons.css";
3
2
  import EditorContent from "./components/tetrons/EditorContent";
4
3
  export default EditorContent;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { default as EditorContent } from "./components/tetrons/EditorContent";
2
- import "./styles/tetrons.css";
2
+ // import "./styles/tetrons.css";
3
3
  import EditorContent from "./components/tetrons/EditorContent";
4
4
  export default EditorContent;
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: `p-2 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${isActive ? "bg-gray-300" : ""}`
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 currentZoom = parseFloat(element.style.zoom || "1");
14406
+ const style = element.style;
14407
+ const currentZoom = parseFloat(style.zoom || "1");
14407
14408
  const next = Math.min(currentZoom + 0.1, 2);
14408
- element.style.zoom = next.toString();
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 currentZoom = parseFloat(element.style.zoom || "1");
14417
+ const style = element.style;
14418
+ const currentZoom = parseFloat(style.zoom || "1");
14417
14419
  const next = Math.max(currentZoom - 0.1, 0.5);
14418
- element.style.zoom = next.toString();
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
  {
@@ -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,2 @@
1
+ import mongoose from "mongoose";
2
+ export declare const ApiKey: mongoose.Model<any, {}, {}, {}, any, any>;
@@ -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
+ }
@@ -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.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",