tetrons 2.3.1 → 2.3.22

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.
Files changed (82) hide show
  1. package/dist/components/components/tetrons/EditorContent.tsx +282 -0
  2. package/dist/components/components/tetrons/ResizableImageComponent.tsx +77 -0
  3. package/dist/components/components/tetrons/ResizableVideoComponent.tsx +56 -0
  4. package/dist/index.d.mts +5 -1
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.mjs +53 -11
  8. package/package.json +9 -9
  9. package/dist/app/api/export/route.d.ts +0 -1
  10. package/dist/app/api/export/route.js +0 -4
  11. package/dist/app/api/register/route.d.ts +0 -7
  12. package/dist/app/api/register/route.js +0 -32
  13. package/dist/app/api/save/route.d.ts +0 -6
  14. package/dist/app/api/save/route.js +0 -15
  15. package/dist/app/api/validate/route.d.ts +0 -10
  16. package/dist/app/api/validate/route.js +0 -58
  17. package/dist/app/layout.d.ts +0 -6
  18. package/dist/app/layout.jsx +0 -30
  19. package/dist/app/page.d.ts +0 -2
  20. package/dist/app/page.jsx +0 -51
  21. package/dist/components/UI/Button.jsx +0 -1
  22. package/dist/components/UI/Dropdown.jsx +0 -1
  23. package/dist/components/tetrons/EditorContent.d.ts +0 -6
  24. package/dist/components/tetrons/EditorContent.jsx +0 -209
  25. package/dist/components/tetrons/ResizableImage.d.ts +0 -1
  26. package/dist/components/tetrons/ResizableImage.js +0 -40
  27. package/dist/components/tetrons/ResizableImageComponent.d.ts +0 -11
  28. package/dist/components/tetrons/ResizableImageComponent.jsx +0 -37
  29. package/dist/components/tetrons/ResizableVideo.d.ts +0 -12
  30. package/dist/components/tetrons/ResizableVideo.js +0 -61
  31. package/dist/components/tetrons/ResizableVideoComponent.d.ts +0 -4
  32. package/dist/components/tetrons/ResizableVideoComponent.jsx +0 -32
  33. package/dist/components/tetrons/helpers.d.ts +0 -0
  34. package/dist/components/tetrons/helpers.js +0 -1
  35. package/dist/components/tetrons/toolbar/ActionGroup.d.ts +0 -7
  36. package/dist/components/tetrons/toolbar/ActionGroup.jsx +0 -167
  37. package/dist/components/tetrons/toolbar/ClipboardGroup.d.ts +0 -5
  38. package/dist/components/tetrons/toolbar/ClipboardGroup.jsx +0 -36
  39. package/dist/components/tetrons/toolbar/FileGroup.d.ts +0 -7
  40. package/dist/components/tetrons/toolbar/FileGroup.jsx +0 -40
  41. package/dist/components/tetrons/toolbar/FontStyleGroup.d.ts +0 -7
  42. package/dist/components/tetrons/toolbar/FontStyleGroup.jsx +0 -104
  43. package/dist/components/tetrons/toolbar/InsertGroup.d.ts +0 -5
  44. package/dist/components/tetrons/toolbar/InsertGroup.jsx +0 -163
  45. package/dist/components/tetrons/toolbar/ListAlignGroup.d.ts +0 -5
  46. package/dist/components/tetrons/toolbar/ListAlignGroup.jsx +0 -16
  47. package/dist/components/tetrons/toolbar/MiscGroup.d.ts +0 -7
  48. package/dist/components/tetrons/toolbar/MiscGroup.jsx +0 -31
  49. package/dist/components/tetrons/toolbar/TableContextMenu.d.ts +0 -7
  50. package/dist/components/tetrons/toolbar/TableContextMenu.jsx +0 -52
  51. package/dist/components/tetrons/toolbar/TetronsToolbar.d.ts +0 -6
  52. package/dist/components/tetrons/toolbar/TetronsToolbar.jsx +0 -46
  53. package/dist/components/tetrons/toolbar/ToolbarButton.d.ts +0 -12
  54. package/dist/components/tetrons/toolbar/ToolbarButton.jsx +0 -8
  55. package/dist/components/tetrons/toolbar/extensions/Comment.d.ts +0 -17
  56. package/dist/components/tetrons/toolbar/extensions/Comment.js +0 -45
  57. package/dist/components/tetrons/toolbar/extensions/Embed.d.ts +0 -2
  58. package/dist/components/tetrons/toolbar/extensions/Embed.js +0 -90
  59. package/dist/components/tetrons/toolbar/extensions/FontFamily.d.ts +0 -9
  60. package/dist/components/tetrons/toolbar/extensions/FontFamily.js +0 -28
  61. package/dist/components/tetrons/toolbar/extensions/FontSize.d.ts +0 -9
  62. package/dist/components/tetrons/toolbar/extensions/FontSize.js +0 -28
  63. package/dist/components/tetrons/toolbar/extensions/ResizableTable.d.ts +0 -1
  64. package/dist/components/tetrons/toolbar/extensions/ResizableTable.js +0 -11
  65. package/dist/components/tetrons/toolbar/marks/Subscript.d.ts +0 -2
  66. package/dist/components/tetrons/toolbar/marks/Subscript.js +0 -35
  67. package/dist/components/tetrons/toolbar/marks/Superscript.d.ts +0 -2
  68. package/dist/components/tetrons/toolbar/marks/Superscript.js +0 -35
  69. package/dist/lib/db.d.ts +0 -1
  70. package/dist/lib/db.js +0 -15
  71. package/dist/lib/export.d.ts +0 -0
  72. package/dist/lib/export.js +0 -1
  73. package/dist/lib/tiptap-extensions.d.ts +0 -0
  74. package/dist/lib/tiptap-extensions.js +0 -1
  75. package/dist/models/ApiKey.d.ts +0 -2
  76. package/dist/models/ApiKey.js +0 -14
  77. package/dist/utils/apiKeyUtils.d.ts +0 -11
  78. package/dist/utils/apiKeyUtils.js +0 -33
  79. package/dist/utils/loadEmojiPicker.d.ts +0 -1
  80. package/dist/utils/loadEmojiPicker.js +0 -12
  81. /package/dist/components/{UI/Button.d.ts → components/UI/Button.tsx} +0 -0
  82. /package/dist/components/{UI/Dropdown.d.ts → components/UI/Dropdown.tsx} +0 -0
@@ -0,0 +1,282 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Comment } from "./toolbar/extensions/Comment";
5
+ import { useEffect, useRef } from "react";
6
+ import {
7
+ useEditor,
8
+ EditorContent as TiptapEditorContent,
9
+ Editor,
10
+ } from "@tiptap/react";
11
+
12
+ import Document from "@tiptap/extension-document";
13
+ import Paragraph from "@tiptap/extension-paragraph";
14
+ import Text from "@tiptap/extension-text";
15
+ import History from "@tiptap/extension-history";
16
+ import Bold from "@tiptap/extension-bold";
17
+ import Italic from "@tiptap/extension-italic";
18
+ import Underline from "@tiptap/extension-underline";
19
+ import Strike from "@tiptap/extension-strike";
20
+ import Code from "@tiptap/extension-code";
21
+ import Blockquote from "@tiptap/extension-blockquote";
22
+ import HardBreak from "@tiptap/extension-hard-break";
23
+ import Heading from "@tiptap/extension-heading";
24
+ import HorizontalRule from "@tiptap/extension-horizontal-rule";
25
+
26
+ import TextAlign from "@tiptap/extension-text-align";
27
+ import Color from "@tiptap/extension-color";
28
+ import Highlight from "@tiptap/extension-highlight";
29
+ import Image from "@tiptap/extension-image";
30
+ import Link from "@tiptap/extension-link";
31
+ import TextStyle from "@tiptap/extension-text-style";
32
+
33
+ import ListItem from "@tiptap/extension-list-item";
34
+ import BulletList from "@tiptap/extension-bullet-list";
35
+ import OrderedList from "@tiptap/extension-ordered-list";
36
+ import { Subscript } from "./toolbar/marks/Subscript";
37
+ import { Superscript } from "./toolbar/marks/Superscript";
38
+
39
+ import { ResizableTable } from "./toolbar/extensions/ResizableTable";
40
+ import { Embed } from "./toolbar/extensions/Embed";
41
+ import TableRow from "@tiptap/extension-table-row";
42
+ import TableCell from "@tiptap/extension-table-cell";
43
+ import TableHeader from "@tiptap/extension-table-header";
44
+
45
+ import { FontFamily } from "./toolbar/extensions/FontFamily";
46
+ import { FontSize } from "./toolbar/extensions/FontSize";
47
+ import TetronsToolbar from "./toolbar/TetronsToolbar";
48
+
49
+ import { ResizableImage } from "./ResizableImage";
50
+ import { ResizableVideo } from "./ResizableVideo";
51
+ import TableContextMenu from "./toolbar/TableContextMenu";
52
+ import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
53
+ import { createLowlight } from "lowlight";
54
+
55
+ import js from "highlight.js/lib/languages/javascript";
56
+ import ts from "highlight.js/lib/languages/typescript";
57
+
58
+ const lowlight = createLowlight();
59
+
60
+ lowlight.register("js", js);
61
+ lowlight.register("ts", ts);
62
+
63
+ type EditorContentProps = {
64
+ apiKey: string;
65
+ };
66
+
67
+ export default function EditorContent({ apiKey }: EditorContentProps) {
68
+ const [isValid, setIsValid] = React.useState<boolean | null>(null);
69
+ const [error, setError] = React.useState<string | null>(null);
70
+ const [versions, setVersions] = React.useState<string[]>([]);
71
+ const [userVersion, setUserVersion] = React.useState<
72
+ "free" | "pro" | "premium" | "platinum" | null
73
+ >(null);
74
+
75
+ const [currentVersionIndex, setCurrentVersionIndex] = React.useState<
76
+ number | null
77
+ >(null);
78
+
79
+ function getApiBaseUrl(): string {
80
+ if (
81
+ typeof import.meta !== "undefined" &&
82
+ import.meta.env?.VITE_TETRONS_API_URL
83
+ ) {
84
+ return import.meta.env.VITE_TETRONS_API_URL;
85
+ }
86
+ if (
87
+ typeof process !== "undefined" &&
88
+ process.env?.NEXT_PUBLIC_TETRONS_API_URL
89
+ ) {
90
+ return process.env.NEXT_PUBLIC_TETRONS_API_URL;
91
+ }
92
+ return "https://staging.tetrons.com";
93
+ }
94
+
95
+ const API_BASE_URL = getApiBaseUrl();
96
+
97
+ useEffect(() => {
98
+ const validateKey = async () => {
99
+ try {
100
+ const res = await fetch(`${API_BASE_URL}/api/validate`, {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ },
105
+ body: JSON.stringify({ apiKey }),
106
+ });
107
+
108
+ const data = await res.json();
109
+ if (!res.ok) throw new Error(data.error || "Invalid API Key");
110
+
111
+ setIsValid(true);
112
+ setUserVersion(data.version);
113
+ } catch (err: unknown) {
114
+ if (err instanceof Error) {
115
+ setError(err.message || "Invalid API Key");
116
+ } else {
117
+ setError("Invalid API Key");
118
+ }
119
+ setIsValid(false);
120
+ }
121
+ };
122
+
123
+ validateKey();
124
+ }, [apiKey]);
125
+
126
+ const editor: Editor | null = useEditor({
127
+ extensions: [
128
+ Document,
129
+ Paragraph,
130
+ Text,
131
+ History,
132
+ Bold,
133
+ Italic,
134
+ Underline,
135
+ Strike,
136
+ Code,
137
+ Blockquote,
138
+ HardBreak,
139
+ Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
140
+ HorizontalRule,
141
+
142
+ TextStyle,
143
+ Color,
144
+ Highlight.configure({ multicolor: true }),
145
+ FontFamily,
146
+ FontSize,
147
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
148
+
149
+ ListItem,
150
+ BulletList,
151
+ OrderedList,
152
+ Subscript,
153
+ Superscript,
154
+
155
+ Image,
156
+ Link.configure({
157
+ openOnClick: false,
158
+ autolink: true,
159
+ linkOnPaste: true,
160
+ }),
161
+
162
+ ResizableTable.configure({
163
+ resizable: true,
164
+ }),
165
+ TableRow,
166
+ TableCell,
167
+ TableHeader,
168
+ Embed,
169
+
170
+ ResizableImage,
171
+ ResizableVideo,
172
+ Comment,
173
+ CodeBlockLowlight.configure({
174
+ lowlight,
175
+ HTMLAttributes: {
176
+ class: "bg-gray-100 p-2 rounded font-mono text-sm overflow-auto",
177
+ },
178
+ }),
179
+ ],
180
+ content: "",
181
+ editorProps: {
182
+ attributes: {
183
+ class: "min-h-full focus:outline-none p-0",
184
+ "data-placeholder": "Start typing here...",
185
+ },
186
+ },
187
+ immediatelyRender: false,
188
+ });
189
+
190
+ const wrapperRef = useRef<HTMLDivElement>(null);
191
+
192
+ useEffect(() => {
193
+ return () => {
194
+ editor?.destroy();
195
+ };
196
+ }, [editor]);
197
+
198
+ const handleEditorClick = () => {
199
+ if (editor && !editor.isFocused) {
200
+ editor.commands.focus();
201
+ }
202
+ };
203
+
204
+ const saveVersion = () => {
205
+ if (!editor) return;
206
+ const content = editor.getJSON();
207
+ setVersions((prev) => [...prev, JSON.stringify(content)]);
208
+ setCurrentVersionIndex(versions.length);
209
+ };
210
+
211
+ const restoreVersion = (index: number) => {
212
+ if (!editor) return;
213
+ const versionContent = versions[index];
214
+ if (versionContent) {
215
+ editor.commands.setContent(JSON.parse(versionContent));
216
+ setCurrentVersionIndex(index);
217
+ }
218
+ };
219
+
220
+ if (isValid === false) {
221
+ return <div className="editor-error">⚠️ {error}</div>;
222
+ }
223
+
224
+ if (isValid === null) {
225
+ return <div className="editor-loading">🔍 Validating license...</div>;
226
+ }
227
+
228
+ return (
229
+ <div className="editor-container">
230
+ {userVersion !== "free" && (
231
+ <div className="editor-toolbar">
232
+ <button
233
+ type="button"
234
+ onClick={saveVersion}
235
+ disabled={!editor}
236
+ className="editor-save-btn"
237
+ >
238
+ Save Version
239
+ </button>
240
+
241
+ <div className="editor-versions-wrapper">
242
+ {versions.length === 0 && (
243
+ <span className="editor-no-versions">No saved versions</span>
244
+ )}
245
+ {versions.map((_, idx) => (
246
+ <button
247
+ type="button"
248
+ key={idx}
249
+ onClick={() => restoreVersion(idx)}
250
+ className={`editor-version-btn ${
251
+ idx === currentVersionIndex ? "active" : ""
252
+ }`}
253
+ title={`Restore Version ${idx + 1}`}
254
+ >
255
+ {`V${idx + 1}`}
256
+ </button>
257
+ ))}
258
+ </div>
259
+ </div>
260
+ )}
261
+
262
+ {editor && userVersion && (
263
+ <TetronsToolbar editor={editor} version={userVersion} />
264
+ )}
265
+
266
+ <div
267
+ ref={wrapperRef}
268
+ className="editor-content-wrapper"
269
+ onClick={handleEditorClick}
270
+ >
271
+ {editor ? (
272
+ <>
273
+ <TiptapEditorContent editor={editor} />
274
+ {editor && <TableContextMenu editor={editor} />}
275
+ </>
276
+ ) : (
277
+ <div className="editor-loading">Loading editor...</div>
278
+ )}
279
+ </div>
280
+ </div>
281
+ );
282
+ }
@@ -0,0 +1,77 @@
1
+ import React, { useRef, useEffect } from "react";
2
+ import { NodeViewWrapper, NodeViewRendererProps } from "@tiptap/react";
3
+
4
+ interface ResizableImageProps extends NodeViewRendererProps {
5
+ updateAttributes: (attrs: {
6
+ width?: number | null;
7
+ height?: number | null;
8
+ }) => void;
9
+ selected?: boolean;
10
+ }
11
+
12
+ const ResizableImageComponent: React.FC<ResizableImageProps> = ({
13
+ node,
14
+ updateAttributes,
15
+ selected,
16
+ }) => {
17
+ const { src, alt, title, width, height } = node.attrs as {
18
+ src: string;
19
+ alt?: string;
20
+ title?: string;
21
+ width?: number | null;
22
+ height?: number | null;
23
+ };
24
+ const wrapperRef = useRef<HTMLDivElement>(null);
25
+ const imgRef = useRef<HTMLImageElement>(null);
26
+
27
+ useEffect(() => {
28
+ const img = imgRef.current;
29
+ if (!img) return;
30
+
31
+ const observer = new ResizeObserver(() => {
32
+ const w = Math.round(img.offsetWidth);
33
+ const h = Math.round(img.offsetHeight);
34
+ updateAttributes({ width: w, height: h });
35
+ });
36
+
37
+ observer.observe(img);
38
+ return () => observer.disconnect();
39
+ }, [updateAttributes]);
40
+
41
+ return (
42
+ <NodeViewWrapper
43
+ ref={wrapperRef}
44
+ contentEditable={false}
45
+ className={`resizable-image-wrapper ${
46
+ selected ? "ProseMirror-selectednode" : ""
47
+ }`}
48
+ style={{
49
+ resize: "both",
50
+ overflow: "auto",
51
+ border: "1px solid #ccc",
52
+ padding: 2,
53
+ display: "inline-block",
54
+ maxWidth: "100%",
55
+ }}
56
+ >
57
+ {/* eslint-disable-next-line @next/next/no-img-element */}
58
+ <img
59
+ ref={imgRef}
60
+ src={src}
61
+ alt={alt ?? ""}
62
+ title={title ?? ""}
63
+ loading="lazy"
64
+ style={{
65
+ width: width ? `${width}px` : "auto",
66
+ height: height ? `${height}px` : "auto",
67
+ display: "block",
68
+ userSelect: "none",
69
+ pointerEvents: "auto",
70
+ }}
71
+ draggable={false}
72
+ />
73
+ </NodeViewWrapper>
74
+ );
75
+ };
76
+
77
+ export default ResizableImageComponent;
@@ -0,0 +1,56 @@
1
+ import React, { useRef, useEffect } from "react";
2
+ import { NodeViewWrapper } from "@tiptap/react";
3
+ import type { NodeViewProps } from "@tiptap/core";
4
+
5
+ const ResizableVideoComponent: React.FC<NodeViewProps> = ({
6
+ node,
7
+ updateAttributes,
8
+ selected,
9
+ }) => {
10
+ const { src, controls, width, height } = node.attrs;
11
+ const wrapperRef = useRef<HTMLDivElement>(null);
12
+ const videoRef = useRef<HTMLVideoElement>(null);
13
+
14
+ useEffect(() => {
15
+ const video = videoRef.current;
16
+ if (!video) return;
17
+
18
+ const observer = new ResizeObserver(() => {
19
+ const w = Math.round(video.offsetWidth);
20
+ const h = Math.round(video.offsetHeight);
21
+ updateAttributes({ width: w, height: h });
22
+ });
23
+
24
+ observer.observe(video);
25
+ return () => observer.disconnect();
26
+ }, [updateAttributes]);
27
+
28
+ return (
29
+ <NodeViewWrapper
30
+ ref={wrapperRef}
31
+ contentEditable={false}
32
+ className={`resizable-video-wrapper ${
33
+ selected ? "ProseMirror-selectednode" : ""
34
+ }`}
35
+ style={{
36
+ resize: "both",
37
+ overflow: "auto",
38
+ border: "1px solid #ccc",
39
+ padding: "2px",
40
+ display: "inline-block",
41
+ }}
42
+ >
43
+ <video
44
+ ref={videoRef}
45
+ src={src}
46
+ controls={controls}
47
+ style={{
48
+ width: width ? `${width}px` : "auto",
49
+ height: height ? `${height}px` : "auto",
50
+ }}
51
+ />
52
+ </NodeViewWrapper>
53
+ );
54
+ };
55
+
56
+ export default ResizableVideoComponent;
package/dist/index.d.mts CHANGED
@@ -5,4 +5,8 @@ type EditorContentProps = {
5
5
  };
6
6
  declare function EditorContent({ apiKey }: EditorContentProps): React.JSX.Element;
7
7
 
8
- export { EditorContent, EditorContent as default };
8
+ declare function initializeTetrons(apiKey: string): Promise<void>;
9
+ declare function getTetronsVersion(): "" | "free" | "pro" | "premium" | "platinum";
10
+ declare function isApiKeyValid(): boolean;
11
+
12
+ export { EditorContent, EditorContent as default, getTetronsVersion, initializeTetrons, isApiKeyValid };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import EditorContent from "./components/tetrons/EditorContent";
2
- import "./styles/tetrons.css";
3
2
  export declare function initializeTetrons(apiKey: string): Promise<void>;
4
3
  export declare function getTetronsVersion(): "" | "free" | "pro" | "premium" | "platinum";
5
4
  export declare function isApiKeyValid(): boolean;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import EditorContent from "./components/tetrons/EditorContent";
2
- import "./styles/tetrons.css";
2
+ // import "./styles/tetrons.css";
3
3
  let API_VALID = false;
4
4
  let API_VERSION = "";
5
5
  export async function initializeTetrons(apiKey) {
package/dist/index.mjs CHANGED
@@ -14345,7 +14345,7 @@ var FontSize = Mark2.create({
14345
14345
  });
14346
14346
 
14347
14347
  // src/components/tetrons/toolbar/TetronsToolbar.tsx
14348
- import React9, { useEffect as useEffect3 } from "react";
14348
+ import React9, { useEffect as useEffect3, useState as useState4 } from "react";
14349
14349
 
14350
14350
  // src/components/tetrons/toolbar/ActionGroup.tsx
14351
14351
  import React2, { useEffect, useRef, useState } from "react";
@@ -15233,8 +15233,11 @@ function FileGroup({ editor }) {
15233
15233
  }
15234
15234
 
15235
15235
  // src/components/tetrons/toolbar/TetronsToolbar.tsx
15236
- function TetronsToolbar({ editor }) {
15237
- const [autoSave, setAutoSave] = React9.useState(false);
15236
+ function TetronsToolbar({
15237
+ editor,
15238
+ version
15239
+ }) {
15240
+ const [autoSave, setAutoSave] = useState4(false);
15238
15241
  useEffect3(() => {
15239
15242
  if (!editor) return;
15240
15243
  const handleUpdate = () => {
@@ -15251,7 +15254,7 @@ function TetronsToolbar({ editor }) {
15251
15254
  editor.off("update", handleUpdate);
15252
15255
  };
15253
15256
  }, [autoSave, editor]);
15254
- return /* @__PURE__ */ React9.createElement("div", { className: "tetrons-toolbar" }, /* @__PURE__ */ React9.createElement("div", { className: "group" }, /* @__PURE__ */ React9.createElement(
15257
+ return /* @__PURE__ */ React9.createElement("div", { className: "tetrons-toolbar" }, version !== "free" && /* @__PURE__ */ React9.createElement("div", { className: "group" }, /* @__PURE__ */ React9.createElement(
15255
15258
  "input",
15256
15259
  {
15257
15260
  type: "checkbox",
@@ -15259,7 +15262,7 @@ function TetronsToolbar({ editor }) {
15259
15262
  checked: autoSave,
15260
15263
  onChange: (e) => setAutoSave(e.target.checked)
15261
15264
  }
15262
- ), /* @__PURE__ */ React9.createElement("label", { htmlFor: "autoSave" }, "Auto Save")), /* @__PURE__ */ React9.createElement(FileGroup, { editor }), /* @__PURE__ */ React9.createElement(ClipboardGroup, { editor }), /* @__PURE__ */ React9.createElement(FontStyleGroup, { editor }), /* @__PURE__ */ React9.createElement(ListAlignGroup, { editor }), /* @__PURE__ */ React9.createElement(InsertGroup, { editor }), /* @__PURE__ */ React9.createElement(MiscGroup, { editor }), /* @__PURE__ */ React9.createElement(ActionGroup, { editor }));
15265
+ ), /* @__PURE__ */ React9.createElement("label", { htmlFor: "autoSave" }, "Auto Save")), ["pro", "premium", "platinum"].includes(version) && /* @__PURE__ */ React9.createElement(FileGroup, { editor }), /* @__PURE__ */ React9.createElement(ClipboardGroup, { editor }), /* @__PURE__ */ React9.createElement(FontStyleGroup, { editor }), /* @__PURE__ */ React9.createElement(ListAlignGroup, { editor }), ["premium", "platinum"].includes(version) && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(InsertGroup, { editor }), /* @__PURE__ */ React9.createElement(ActionGroup, { editor })), version === "platinum" && /* @__PURE__ */ React9.createElement(MiscGroup, { editor }));
15263
15266
  }
15264
15267
 
15265
15268
  // src/components/tetrons/ResizableImage.ts
@@ -15460,9 +15463,9 @@ var ResizableVideo = Node2.create({
15460
15463
  });
15461
15464
 
15462
15465
  // src/components/tetrons/toolbar/TableContextMenu.tsx
15463
- import React12, { useEffect as useEffect6, useState as useState4 } from "react";
15466
+ import React12, { useEffect as useEffect6, useState as useState5 } from "react";
15464
15467
  function TableContextMenu({ editor }) {
15465
- const [menuPosition, setMenuPosition] = useState4(null);
15468
+ const [menuPosition, setMenuPosition] = useState5(null);
15466
15469
  useEffect6(() => {
15467
15470
  const handleContextMenu = (event) => {
15468
15471
  const target = event.target;
@@ -17072,11 +17075,22 @@ function EditorContent({ apiKey }) {
17072
17075
  const [isValid, setIsValid] = React13.useState(null);
17073
17076
  const [error, setError] = React13.useState(null);
17074
17077
  const [versions, setVersions] = React13.useState([]);
17078
+ const [userVersion, setUserVersion] = React13.useState(null);
17075
17079
  const [currentVersionIndex, setCurrentVersionIndex] = React13.useState(null);
17080
+ function getApiBaseUrl() {
17081
+ if (typeof import.meta !== "undefined" && import.meta.env?.VITE_TETRONS_API_URL) {
17082
+ return import.meta.env.VITE_TETRONS_API_URL;
17083
+ }
17084
+ if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_TETRONS_API_URL) {
17085
+ return process.env.NEXT_PUBLIC_TETRONS_API_URL;
17086
+ }
17087
+ return "https://staging.tetrons.com";
17088
+ }
17089
+ const API_BASE_URL = getApiBaseUrl();
17076
17090
  useEffect7(() => {
17077
17091
  const validateKey = async () => {
17078
17092
  try {
17079
- const res = await fetch("/api/validate", {
17093
+ const res = await fetch(`${API_BASE_URL}/api/validate`, {
17080
17094
  method: "POST",
17081
17095
  headers: {
17082
17096
  "Content-Type": "application/json"
@@ -17086,6 +17100,7 @@ function EditorContent({ apiKey }) {
17086
17100
  const data = await res.json();
17087
17101
  if (!res.ok) throw new Error(data.error || "Invalid API Key");
17088
17102
  setIsValid(true);
17103
+ setUserVersion(data.version);
17089
17104
  } catch (err) {
17090
17105
  if (err instanceof Error) {
17091
17106
  setError(err.message || "Invalid API Key");
@@ -17186,7 +17201,7 @@ function EditorContent({ apiKey }) {
17186
17201
  if (isValid === null) {
17187
17202
  return /* @__PURE__ */ React13.createElement("div", { className: "editor-loading" }, "\u{1F50D} Validating license...");
17188
17203
  }
17189
- return /* @__PURE__ */ React13.createElement("div", { className: "editor-container" }, /* @__PURE__ */ React13.createElement("div", { className: "editor-toolbar" }, /* @__PURE__ */ React13.createElement(
17204
+ return /* @__PURE__ */ React13.createElement("div", { className: "editor-container" }, userVersion !== "free" && /* @__PURE__ */ React13.createElement("div", { className: "editor-toolbar" }, /* @__PURE__ */ React13.createElement(
17190
17205
  "button",
17191
17206
  {
17192
17207
  type: "button",
@@ -17205,7 +17220,7 @@ function EditorContent({ apiKey }) {
17205
17220
  title: `Restore Version ${idx + 1}`
17206
17221
  },
17207
17222
  `V${idx + 1}`
17208
- )))), editor && /* @__PURE__ */ React13.createElement(TetronsToolbar, { editor }), /* @__PURE__ */ React13.createElement(
17223
+ )))), editor && userVersion && /* @__PURE__ */ React13.createElement(TetronsToolbar, { editor, version: userVersion }), /* @__PURE__ */ React13.createElement(
17209
17224
  "div",
17210
17225
  {
17211
17226
  ref: wrapperRef,
@@ -17217,8 +17232,35 @@ function EditorContent({ apiKey }) {
17217
17232
  }
17218
17233
 
17219
17234
  // src/index.ts
17235
+ var API_VALID = false;
17236
+ var API_VERSION = "";
17237
+ async function initializeTetrons(apiKey) {
17238
+ const res = await fetch("https://staging.tetrons.com/api/validate", {
17239
+ method: "POST",
17240
+ headers: {
17241
+ "Content-Type": "application/json"
17242
+ },
17243
+ body: JSON.stringify({ apiKey })
17244
+ });
17245
+ if (!res.ok) {
17246
+ const error = await res.json();
17247
+ throw new Error(`API Key validation failed: ${error.error}`);
17248
+ }
17249
+ const data = await res.json();
17250
+ API_VALID = data.valid;
17251
+ API_VERSION = data.version;
17252
+ }
17253
+ function getTetronsVersion() {
17254
+ return API_VERSION;
17255
+ }
17256
+ function isApiKeyValid() {
17257
+ return API_VALID;
17258
+ }
17220
17259
  var index_default = EditorContent;
17221
17260
  export {
17222
17261
  EditorContent,
17223
- index_default as default
17262
+ index_default as default,
17263
+ getTetronsVersion,
17264
+ initializeTetrons,
17265
+ isApiKeyValid
17224
17266
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tetrons",
3
- "version": "2.3.1",
3
+ "version": "2.3.22",
4
4
  "description": "A Next.js project written in TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -39,14 +39,14 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@eslint/eslintrc": "^3",
42
- "@tailwindcss/postcss": "^4",
42
+ "@tailwindcss/postcss": "^4.1.11",
43
43
  "@types/node": "^20",
44
44
  "@types/react": "^19",
45
45
  "@types/react-dom": "^19",
46
+ "autoprefixer": "^10.4.21",
46
47
  "copyfiles": "^2.4.1",
47
48
  "eslint": "^9",
48
49
  "eslint-config-next": "^15.3.2",
49
- "tailwindcss": "^4",
50
50
  "tsup": "^8.5.0",
51
51
  "typescript": "^5"
52
52
  },
@@ -63,13 +63,13 @@
63
63
  "author": "Your Name",
64
64
  "license": "MIT",
65
65
  "exports": {
66
- ".": {
67
- "import": "./dist/index.js",
68
- "require": "./dist/index.js",
69
- "types": "./dist/index.d.ts"
70
- },
71
- "./style.css": "./dist/styles/tetrons.css"
66
+ ".": {
67
+ "import": "./dist/index.js",
68
+ "require": "./dist/index.js",
69
+ "types": "./dist/index.d.ts"
72
70
  },
71
+ "./style.css": "./dist/styles/tetrons.css"
72
+ },
73
73
  "files": [
74
74
  "dist",
75
75
  "dist/styles/tetrons.css",
@@ -1 +0,0 @@
1
- export declare function GET(req: Request): Response;
@@ -1,4 +0,0 @@
1
- export function GET(req) {
2
- console.log(req.method); // Example usage
3
- return new Response("Hello, world!", { status: 200 });
4
- }
@@ -1,7 +0,0 @@
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
- expiresAt: Date | null;
7
- }>>;
@@ -1,32 +0,0 @@
1
- import { NextResponse } from "next/server";
2
- import { connectDB } from "../../../lib/db";
3
- import { ApiKey } from "../../../models/ApiKey";
4
- import { generateUserApiKey, getFreeApiKey } from "../../../utils/apiKeyUtils";
5
- export async function POST(req) {
6
- const body = await req.json();
7
- const { email, organization, version } = body;
8
- if (!email || !organization || !version) {
9
- return NextResponse.json({ error: "Missing fields" }, { status: 400 });
10
- }
11
- await connectDB();
12
- const lowerEmail = email.trim().toLowerCase();
13
- const lowerOrg = organization.trim().toLowerCase();
14
- await ApiKey.deleteMany({ email: lowerEmail, version });
15
- let apiKey;
16
- let expiresAt = null;
17
- if (version === "free") {
18
- apiKey = getFreeApiKey();
19
- expiresAt = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
20
- }
21
- else {
22
- apiKey = generateUserApiKey(lowerEmail, lowerOrg, version);
23
- }
24
- await ApiKey.create({
25
- email: lowerEmail,
26
- organization: lowerOrg,
27
- version,
28
- apiKey,
29
- expiresAt,
30
- });
31
- return NextResponse.json({ apiKey, expiresAt });
32
- }
@@ -1,6 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- export declare function POST(request: NextRequest): Promise<NextResponse<{
3
- message: string;
4
- }> | NextResponse<{
5
- error: string;
6
- }>>;