diff --git a/bun.lock b/bun.lock
index 8f2fcf2f..6a0ca50e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -21,11 +21,12 @@
"@react-pdf/renderer": "^3.4.4",
"@tabler/icons-react": "^3.31.0",
"@tiptap/extension-highlight": "^2.2.3",
+ "@tiptap/extension-image": "^2.11.7",
"@tiptap/extension-link": "^2.2.3",
"@tiptap/extension-subscript": "^2.2.3",
"@tiptap/extension-superscript": "^2.2.3",
"@tiptap/extension-text-align": "^2.2.3",
- "@tiptap/extension-underline": "^2.2.3",
+ "@tiptap/extension-underline": "^2.11.7",
"@tiptap/pm": "^2.2.3",
"@tiptap/react": "^2.2.3",
"@tiptap/starter-kit": "^2.2.3",
@@ -955,6 +956,8 @@
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@2.11.5", "", { "peerDependencies": { "@tiptap/core": "^2.7.0", "@tiptap/pm": "^2.7.0" } }, "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A=="],
+ "@tiptap/extension-image": ["@tiptap/extension-image@2.11.7", "", { "peerDependencies": { "@tiptap/core": "^2.7.0" } }, "sha512-YvCmTDB7Oo+A56tR4S/gcNaYpqU4DDlSQcRp5IQvmQV5EekSe0lnEazGDoqOCwsit9qQhj4MPQJhKrnaWrJUrg=="],
+
"@tiptap/extension-italic": ["@tiptap/extension-italic@2.11.5", "", { "peerDependencies": { "@tiptap/core": "^2.7.0" } }, "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA=="],
"@tiptap/extension-link": ["@tiptap/extension-link@2.11.5", "", { "dependencies": { "linkifyjs": "^4.2.0" }, "peerDependencies": { "@tiptap/core": "^2.7.0", "@tiptap/pm": "^2.7.0" } }, "sha512-4Iu/aPzevbYpe50xDI0ZkqRa6nkZ9eF270Ue2qaF3Ab47nehj+9Jl78XXzo8+LTyFMnrETI73TAs1aC/IGySeQ=="],
@@ -977,7 +980,7 @@
"@tiptap/extension-text-style": ["@tiptap/extension-text-style@2.11.5", "", { "peerDependencies": { "@tiptap/core": "^2.7.0" } }, "sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ=="],
- "@tiptap/extension-underline": ["@tiptap/extension-underline@2.11.5", "", { "peerDependencies": { "@tiptap/core": "^2.7.0" } }, "sha512-YpWHXNIkSoRSuzT2cvgKpyJ2tTz3LzqkTM64uC+uTJ8cUkvXIWUWejJR42q8ma/mTlQe4lHff4IQ0Sf58Digtw=="],
+ "@tiptap/extension-underline": ["@tiptap/extension-underline@2.11.7", "", { "peerDependencies": { "@tiptap/core": "^2.7.0" } }, "sha512-NtoQw6PGijOAtXC6G+0Aq0/Z5wwEjPhNHs8nsjXogfWIgaj/aI4/zfBnA06eI3WT+emMYQTl0fTc4CUPnLVU8g=="],
"@tiptap/pm": ["@tiptap/pm@2.11.5", "", { "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.23.0", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.3", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.37.0" } }, "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw=="],
diff --git a/package.json b/package.json
index 745edb3d..fd8c5fdd 100644
--- a/package.json
+++ b/package.json
@@ -32,11 +32,12 @@
"@react-pdf/renderer": "^3.4.4",
"@tabler/icons-react": "^3.31.0",
"@tiptap/extension-highlight": "^2.2.3",
+ "@tiptap/extension-image": "^2.11.7",
"@tiptap/extension-link": "^2.2.3",
"@tiptap/extension-subscript": "^2.2.3",
"@tiptap/extension-superscript": "^2.2.3",
"@tiptap/extension-text-align": "^2.2.3",
- "@tiptap/extension-underline": "^2.2.3",
+ "@tiptap/extension-underline": "^2.11.7",
"@tiptap/pm": "^2.2.3",
"@tiptap/react": "^2.2.3",
"@tiptap/starter-kit": "^2.2.3",
diff --git a/src/app/dev/forum/detail/main-detail/[id]/page.tsx b/src/app/dev/forum/detail/main-detail/[id]/page.tsx
index 596596bb..325f4bb7 100644
--- a/src/app/dev/forum/detail/main-detail/[id]/page.tsx
+++ b/src/app/dev/forum/detail/main-detail/[id]/page.tsx
@@ -3,10 +3,10 @@ import Forum_MainDetail from "@/app_modules/forum/detail/main_detail";
export default async function Page() {
const userLoginId = await funGetUserIdByToken();
-
return (
<>
+ {/* */}
>
);
}
diff --git a/src/app/zCoba/text_editor/page.tsx b/src/app/zCoba/text_editor/page.tsx
new file mode 100644
index 00000000..76fc18ac
--- /dev/null
+++ b/src/app/zCoba/text_editor/page.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import { useEditor, EditorContent } from "@tiptap/react";
+import StarterKit from "@tiptap/starter-kit";
+import Image from "@tiptap/extension-image";
+import { useState } from "react";
+import {
+ Box,
+ Button,
+ Group,
+ Image as MantineImage,
+ Stack,
+ Text,
+} from "@mantine/core";
+import Underline from "@tiptap/extension-underline";
+import { MainColor } from "@/app_modules/_global/color";
+
+const listStiker = [
+ {
+ id: 2,
+ name: "stiker2",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQN9AKmsBY4yqdn3GueJJEVPJbfmf853gDL4cN8uc9eqsCTiJ1fzhcpywzVP68NCJEA5NQ&usqp=CAU",
+ },
+ {
+ id: 3,
+ name: "stiker3",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2lkV3ZiQ8m-OELSui2JGVy80vnh1cyRUV7NrgFNluPVVs2HUAyCHwCMAKGe2s5jk2sn8&usqp=CAU",
+ },
+ {
+ id: 4,
+ name: "stiker4",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQHy9ZdsPc6dHgVTl5yIGpRJ-KtpTIsXA2_kbfO1Oc-pv_f7CNKGxhO56RjKujE3xCyb9k&usqp=CAU",
+ },
+];
+
+export default function RichTextWithStickers() {
+ const [chat, setChat] = useState([]);
+
+ const editor = useEditor({
+ extensions: [
+ StarterKit, // Sudah include Bold, Italic, dll
+ Underline, // Tambahan untuk underline
+ Image,
+ ],
+ content: "",
+ });
+
+ const insertSticker = (url: string) => {
+ editor?.chain().focus().setImage({ src: url }).run();
+ };
+
+ return (
+
+ Tiptap Editor dengan Stiker Inline
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {listStiker.map((item) => (
+ insertSticker(item.url)}
+ style={{
+ border: "none",
+ background: "transparent",
+ cursor: "pointer",
+ }}
+ >
+
+
+ ))}
+
+
+ {/*
+ {chat.map((item, index) => (
+
+ ))}
+ */}
+
+ );
+}
diff --git a/src/app/zCoba/text_editor2/page.tsx b/src/app/zCoba/text_editor2/page.tsx
new file mode 100644
index 00000000..b868b363
--- /dev/null
+++ b/src/app/zCoba/text_editor2/page.tsx
@@ -0,0 +1,226 @@
+"use client";
+import React, { useState, useEffect } from "react";
+import {
+ Box,
+ Button,
+ Group,
+ Image,
+ Paper,
+ ScrollArea,
+ SimpleGrid,
+ Stack,
+ Text,
+ Tooltip,
+ Modal,
+} from "@mantine/core";
+import { useDisclosure } from "@mantine/hooks";
+import dynamic from "next/dynamic";
+import { MainColor } from "@/app_modules/_global/color";
+
+// Dynamic import ReactQuill dengan SSR disabled
+const ReactQuill = dynamic(
+ async () => {
+ const { default: RQ } = await import("react-quill");
+ // Tidak perlu import CSS dengan import statement
+ return function comp({ forwardedRef, ...props }: any) {
+ return ;
+ };
+ },
+ { ssr: false, loading: () => Loading Editor...
}
+);
+
+const listStiker = [
+ {
+ id: 1,
+ name: "stiker2",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQN9AKmsBY4yqdn3GueJJEVPJbfmf853gDL4cN8uc9eqsCTiJ1fzhcpywzVP68NCJEA5NQ&usqp=CAU",
+ },
+ {
+ id: 2,
+ name: "stiker3",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2lkV3ZiQ8m-OELSui2JGVy80vnh1cyRUV7NrgFNluPVVs2HUAyCHwCMAKGe2s5jk2sn8&usqp=CAU",
+ },
+ {
+ id: 3,
+ name: "stiker4",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQHy9ZdsPc6dHgVTl5yIGpRJ-KtpTIsXA2_kbfO1Oc-pv_f7CNKGxhO56RjKujE3xCyb9k&usqp=CAU",
+ },
+];
+
+type ChatItem = {
+ content: string; // HTML content including text and stickers
+};
+
+export default function Page() {
+ const [editorContent, setEditorContent] = useState("");
+ const [chat, setChat] = useState([]);
+ const [opened, { open, close }] = useDisclosure(false);
+ const quillRef = React.useRef(null);
+ const [quillLoaded, setQuillLoaded] = useState(false);
+
+ // Load CSS on client-side only
+ useEffect(() => {
+ // Add Quill CSS via tag
+ const link = document.createElement("link");
+ link.href = "https://cdn.quilljs.com/1.3.6/quill.snow.css";
+ link.rel = "stylesheet";
+ document.head.appendChild(link);
+
+ // Add custom style for stickers inside Quill editor
+ const style = document.createElement("style");
+ style.textContent = `
+ .ql-editor img {
+ max-width: 40px !important;
+ max-height: 40px !important;
+ }
+ .chat-content img {
+ max-width: 40px !important;
+ max-height: 40px !important;
+ }
+ `;
+ document.head.appendChild(style);
+
+ setQuillLoaded(true);
+
+ return () => {
+ // Clean up when component unmounts
+ document.head.removeChild(link);
+ document.head.removeChild(style);
+ };
+ }, []);
+
+ // Custom toolbar options for ReactQuill
+ const modules = {
+ toolbar: [
+ [{ header: [1, 2, false] }],
+ ["bold", "italic", "underline", "strike", "blockquote"],
+ [{ list: "ordered" }, { list: "bullet" }],
+ ["link", "image"],
+ ["clean"],
+ ],
+ };
+
+ const formats = [
+ "header",
+ "bold",
+ "italic",
+ "underline",
+ "strike",
+ "blockquote",
+ "list",
+ "bullet",
+ "link",
+ "image",
+ ];
+
+ const insertSticker = (stickerUrl: string) => {
+ if (!quillRef.current) return;
+
+ const quill = quillRef.current.getEditor();
+ const range = quill.getSelection(true);
+
+ // Custom image insertion with size
+ // Use custom blot or HTML string with size attributes
+ const stickerHtml = `
`;
+
+ // Insert HTML at cursor position
+ quill.clipboard.dangerouslyPasteHTML(range.index, stickerHtml);
+
+ // Move cursor after inserted sticker
+ quill.setSelection(range.index + 1, 0);
+
+ // Focus back on editor
+ quill.focus();
+
+ // Close sticker modal
+ close();
+ };
+
+ // Function to send message
+ const sendMessage = () => {
+ if (editorContent.trim() !== "") {
+ setChat((prev) => [...prev, { content: editorContent }]);
+ setEditorContent(""); // Clear after sending
+ }
+ };
+
+ return (
+
+
+
+
+
+ {chat.map((item, index) => (
+
+
+
+ ))}
+
+
+
+
+
+ Chat Preview Data:
+
+
+
+ {JSON.stringify(chat, null, 2)}
+
+
+
+
+
+
+
+ {quillLoaded && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {/* Sticker Modal */}
+
+
+ {listStiker.map((item) => (
+
+
+ insertSticker(item.url)}
+ />
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/app_modules/_global/lib/stiker.ts b/src/app_modules/_global/lib/stiker.ts
new file mode 100644
index 00000000..237cf009
--- /dev/null
+++ b/src/app_modules/_global/lib/stiker.ts
@@ -0,0 +1,17 @@
+export const listStiker = [
+ {
+ id: 1,
+ name: "stiker2",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQN9AKmsBY4yqdn3GueJJEVPJbfmf853gDL4cN8uc9eqsCTiJ1fzhcpywzVP68NCJEA5NQ&usqp=CAU",
+ },
+ {
+ id: 2,
+ name: "stiker3",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2lkV3ZiQ8m-OELSui2JGVy80vnh1cyRUV7NrgFNluPVVs2HUAyCHwCMAKGe2s5jk2sn8&usqp=CAU",
+ },
+ {
+ id: 3,
+ name: "stiker4",
+ url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQHy9ZdsPc6dHgVTl5yIGpRJ-KtpTIsXA2_kbfO1Oc-pv_f7CNKGxhO56RjKujE3xCyb9k&usqp=CAU",
+ },
+];
\ No newline at end of file
diff --git a/src/app_modules/forum/component/detail_component/comp_create_commentar.tsx b/src/app_modules/forum/component/detail_component/comp_create_commentar.tsx
new file mode 100644
index 00000000..e3033ee6
--- /dev/null
+++ b/src/app_modules/forum/component/detail_component/comp_create_commentar.tsx
@@ -0,0 +1,133 @@
+"use client";
+
+import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown";
+import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil";
+import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal";
+import notifikasiToUser_funCreate from "@/app_modules/notifikasi/fun/create/create_notif_to_user";
+import { Stack, Paper, Group, Button, Divider } from "@mantine/core";
+import { useState } from "react";
+import dynamic from "next/dynamic";
+const ReactQuill = dynamic(
+ () => {
+ return import("react-quill");
+ },
+ { ssr: false }
+);
+import { forum_funCreateKomentar } from "../../fun/create/fun_create_komentar";
+import { forum_funGetAllKomentarById } from "../../fun/get/get_all_komentar_by_id";
+import { MODEL_FORUM_POSTING } from "../../model/interface";
+import { useParams, useRouter } from "next/navigation";
+import { MainColor } from "@/app_modules/_global/color/color_pallet";
+import mqtt_client from "@/util/mqtt_client";
+import backendLogger from "@/util/backendLogger";
+import { clientLogger } from "@/util/clientLogger";
+export default function Forum_CompCreateComment({
+ data,
+ userLoginId,
+ onSetNewKomentar,
+}: {
+ data: MODEL_FORUM_POSTING;
+ userLoginId: string;
+ onSetNewKomentar: (val: string) => void;
+}) {
+ const param = useParams<{ id: string }>();
+ const postingId = param.id;
+ const [value, setValue] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [isEmpty, setIsEmpty] = useState(false);
+
+ async function onComment() {
+ if (value.length > 500) {
+ return null;
+ }
+
+ try {
+ setLoading(true);
+ const createComment = await forum_funCreateKomentar(postingId, value);
+ if (createComment.status === 201) {
+ onSetNewKomentar(value);
+ setValue("");
+ setIsEmpty(true);
+ ComponentGlobal_NotifikasiBerhasil(createComment.message, 2000);
+
+ if (userLoginId !== data.Author.id) {
+ const dataNotif = {
+ appId: data.id,
+ userId: data.authorId,
+ pesan: value,
+ kategoriApp: "FORUM",
+ title: "Komentar baru",
+ };
+
+ const createNotifikasi = await notifikasiToUser_funCreate({
+ data: dataNotif as any,
+ });
+
+ if (createNotifikasi.status === 201) {
+ mqtt_client.publish(
+ "USER",
+ JSON.stringify({
+ userId: dataNotif.userId,
+ count: 1,
+ })
+ );
+ }
+ }
+ } else {
+ setLoading(false);
+ ComponentGlobal_NotifikasiGagal(createComment.message);
+ }
+ } catch (error) {
+ setLoading(false);
+ clientLogger.error("Error create komentar forum", error);
+ }
+ }
+
+ return (
+ <>
+
+
+ {
+ setValue(val);
+ }}
+ style={{
+ overflowY: "auto",
+ maxHeight: 100,
+ minHeight: 50,
+ }}
+ />
+
+
+
+
+
" || value.length > 500
+ ? true
+ : false
+ }
+ bg={MainColor.yellow}
+ color={"yellow"}
+ c="black"
+ loaderPosition="center"
+ loading={loading}
+ radius={"xl"}
+ onClick={() => onComment()}
+ >
+ Balas
+
+
+
+ >
+ );
+}
diff --git a/src/app_modules/forum/detail/main_detail.tsx b/src/app_modules/forum/detail/main_detail.tsx
index 58b6de01..7a434242 100644
--- a/src/app_modules/forum/detail/main_detail.tsx
+++ b/src/app_modules/forum/detail/main_detail.tsx
@@ -34,7 +34,9 @@ export default function Forum_MainDetail({
const [dataPosting, setDataPosting] = useState(
null
);
- const [listKomentar, setListKomentar] = useState([]);
+ const [listKomentar, setListKomentar] = useState<
+ MODEL_FORUM_KOMENTAR[] | null
+ >(null);
const [activePage, setActivePage] = useState(1);
const [newKomentar, setNewKomentar] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@@ -130,7 +132,7 @@ export default function Forum_MainDetail({
return (
<>
- {!dataPosting || !listKomentar ? (
+ {!dataPosting ? (
) : (
) : _.isEmpty(listKomentar) ? (
@@ -172,7 +174,7 @@ export default function Forum_MainDetail({
)}
data={listKomentar}
- setData={setListKomentar}
+ setData={setListKomentar as any}
moreData={handleMoreDataKomentar}
>
{(item) => (
diff --git a/src/app_modules/forum/model/interface.tsx b/src/app_modules/forum/model/interface.tsx
index 83c7a8df..0a001618 100644
--- a/src/app_modules/forum/model/interface.tsx
+++ b/src/app_modules/forum/model/interface.tsx
@@ -75,3 +75,8 @@ export interface MODEL_FORUM_REPORT_KOMENTAR {
userId: string;
User: MODEL_USER;
}
+
+export type CommentItem = {
+ deskripsi: string;
+ // Add any other properties that CommentItem should have
+};
\ No newline at end of file