user
4 mins, ago
diff --git a/app/src/components/ui/collaboration/Messages.tsx b/app/src/components/ui/collaboration/Messages.tsx
new file mode 100644
index 0000000..b0bfae4
--- /dev/null
+++ b/app/src/components/ui/collaboration/Messages.tsx
@@ -0,0 +1,80 @@
+import React, { useState } from "react";
+import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
+import { KebabIcon } from "../../icons/ExportCommonIcons";
+
+interface MessageProps {
+ val: {
+ userName: string;
+ userId: string;
+ message: string;
+ creationTime: string;
+ idEdited: boolean;
+ modifiedTime: string;
+ };
+ i: number;
+}
+
+const Messages: React.FC
= ({ val, i }) => {
+ const [isEditing, setIsEditing] = useState(false);
+ const [openOptions, setOpenOptions] = useState(false);
+ const currentUser = "1";
+ return (
+ <>
+ {isEditing ? (
+ <>
+
+ {val.userName[0]}
+
+
+
+
{val.userName}
+
{val.creationTime}
+
+ {val.userId === currentUser && (
+
+
+ {openOptions && (
+
+
+
+
+ )}
+
+ )}
+
+ >
+ ) : (
+
+ )}
+ >
+ );
+};
+
+export default Messages;
diff --git a/app/src/components/ui/collaboration/ThreadChat.tsx b/app/src/components/ui/collaboration/ThreadChat.tsx
new file mode 100644
index 0000000..c1441a2
--- /dev/null
+++ b/app/src/components/ui/collaboration/ThreadChat.tsx
@@ -0,0 +1,63 @@
+import React from "react";
+import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons";
+import Messages from "./Messages";
+import { ExpandIcon } from "../../icons/SimulationIcons";
+
+const ThreadChat: React.FC = () => {
+ const messages = [
+ {
+ userName: "user 1",
+ userId: "1",
+ message: "hello, thread check",
+ creationTime: "2 hrs ago",
+ idEdited: true,
+ modifiedTime: "5 mins ago",
+ },
+ {
+ userName: "user 2",
+ userId: "2",
+ message: "hello, thread check",
+ creationTime: "2 hrs ago",
+ idEdited: true,
+ modifiedTime: "5 mins ago",
+ },
+ ];
+
+ return (
+
+
+
+
Comment
+
+
+
+
Mark as Unread
+
Mark as Resolved
+
Delete Thread
+
+
+
+
+
+ {messages.map((val, i) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ThreadChat;
diff --git a/app/src/modules/collaboration/camera/collabCams.tsx b/app/src/modules/collaboration/camera/collabCams.tsx
index 5c4e8d6..f24d6aa 100644
--- a/app/src/modules/collaboration/camera/collabCams.tsx
+++ b/app/src/modules/collaboration/camera/collabCams.tsx
@@ -1,18 +1,19 @@
import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
-import { useFrame } from "@react-three/fiber";
+import { useFrame, useThree } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import camModel from "../../../assets/gltf-glb/camera face 2.gltf";
import getActiveUsersData from "../../../services/factoryBuilder/collab/getActiveUsers";
-import { useActiveUsers, useSocketStore } from "../../../store/builder/store";
+import { useActiveUsers, useCamMode, useSocketStore } from "../../../store/builder/store";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { useNavigate } from "react-router-dom";
import { Html } from "@react-three/drei";
import CollabUserIcon from "./collabUserIcon";
import useModuleStore from "../../../store/useModuleStore";
import { getAvatarColor } from "../functions/getAvatarColor";
-import { useSelectedUserStore } from "../../../store/useCollabStore";
+import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
+import setCameraView from "../functions/setCameraView";
const CamModelsGroup = () => {
const navigate = useNavigate();
@@ -30,6 +31,28 @@ const CamModelsGroup = () => {
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
+
+ const { camMode } = useCamMode();
+ const { camera, controls } = useThree(); // Access R3F camera and controls
+
+ useEffect(() => {
+ if (camMode !== "FollowPerson") return;
+ // If a user is selected, set the camera view to their location
+ // and update the camera and controls accordingly
+ if (selectedUser?.location) {
+ const { position, rotation, target } = selectedUser.location;
+ if (rotation && target)
+ setCameraView({
+ controls,
+ camera,
+ position,
+ rotation,
+ target,
+ username: selectedUser.name,
+ });
+ }
+ }, [selectedUser, camera, controls, camMode]);
+
const [cams, setCams] = useState([]);
const [models, setModels] = useState<
Record<
@@ -260,11 +283,10 @@ const CamModelsGroup = () => {
textAlign: "center",
fontFamily: "Arial, sans-serif",
display: `${activeModule !== "visualization" ? "" : "none"}`,
- opacity: `${
- selectedUser?.name !== cam.userData.userName && !isPlaying
- ? 1
- : 0
- }`,
+ opacity: `${selectedUser?.name !== cam.userData.userName && !isPlaying
+ ? 1
+ : 0
+ }`,
transition: "opacity .2s ease",
}}
position={[-0.015, 0, 0.7]}
diff --git a/app/src/modules/collaboration/camera/collabUserIcon.tsx b/app/src/modules/collaboration/camera/collabUserIcon.tsx
index 7a9873b..e45d7a5 100644
--- a/app/src/modules/collaboration/camera/collabUserIcon.tsx
+++ b/app/src/modules/collaboration/camera/collabUserIcon.tsx
@@ -1,6 +1,6 @@
import React from "react";
import CustomAvatar from "../users/Avatar";
-import { useSelectedUserStore } from "../../../store/useCollabStore";
+import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore";
import { useCamMode } from "../../../store/builder/store";
interface CollabUserIconProps {
diff --git a/app/src/modules/collaboration/collaboration.tsx b/app/src/modules/collaboration/collaboration.tsx
index 36e7131..d3486a5 100644
--- a/app/src/modules/collaboration/collaboration.tsx
+++ b/app/src/modules/collaboration/collaboration.tsx
@@ -1,34 +1,18 @@
-import React, { useEffect } from "react";
+import React from "react";
import CamModelsGroup from "./camera/collabCams";
-import { useSelectedUserStore } from "../../store/useCollabStore";
-import { useThree } from "@react-three/fiber";
-import setCameraView from "./functions/setCameraView";
-import { useCamMode } from "../../store/builder/store";
+import CommentsGroup from "./comments/commentsGroup";
const Collaboration: React.FC = () => {
- const { selectedUser } = useSelectedUserStore();
- const { camMode } = useCamMode();
- const { camera, controls } = useThree(); // Access R3F camera and controls
- useEffect(() => {
- if (camMode !== "FollowPerson") return;
- // If a user is selected, set the camera view to their location
- // and update the camera and controls accordingly
- if (selectedUser?.location) {
- const { position, rotation, target } = selectedUser.location;
- if (rotation && target)
- setCameraView({
- controls,
- camera,
- position,
- rotation,
- target,
- username: selectedUser.name,
- });
- }
- }, [selectedUser, camera, controls, camMode]);
+ return (
+ <>
- return ;
+
+
+
+
+ >
+ );
};
export default Collaboration;
diff --git a/app/src/modules/collaboration/comments/commentsGroup.tsx b/app/src/modules/collaboration/comments/commentsGroup.tsx
new file mode 100644
index 0000000..6c10f3d
--- /dev/null
+++ b/app/src/modules/collaboration/comments/commentsGroup.tsx
@@ -0,0 +1,124 @@
+import { useEffect, useState } from "react";
+import { useActiveTool } from "../../../store/builder/store"
+import { useThree } from "@react-three/fiber";
+import { MathUtils, Vector3 } from "three";
+import { useCommentStore } from "../../../store/collaboration/useCommentStore";
+import CommentInstances from "./instances/commentInstances";
+import { Sphere } from "@react-three/drei";
+
+function CommentsGroup() {
+ const { gl, raycaster, camera, scene, pointer } = useThree();
+ const { activeTool } = useActiveTool();
+ const { addComment } = useCommentStore();
+ const [hoverPos, setHoverPos] = useState(null);
+
+ const userId = localStorage.getItem('userId') ?? '';
+
+ useEffect(() => {
+ const canvasElement = gl.domElement;
+
+ let drag = false;
+ let isLeftMouseDown = false;
+
+ const onMouseDown = (evt: MouseEvent) => {
+ if (evt.button === 0) {
+ isLeftMouseDown = true;
+ drag = false;
+ }
+ };
+
+ const onMouseUp = (evt: MouseEvent) => {
+ if (evt.button === 0) {
+ isLeftMouseDown = false;
+ }
+ }
+
+ const onMouseMove = () => {
+ if (isLeftMouseDown) {
+ drag = true;
+ }
+
+ raycaster.setFromCamera(pointer, camera);
+ const intersects = raycaster
+ .intersectObjects(scene.children, true)
+ .filter(
+ (intersect) =>
+ !intersect.object.name.includes("Roof") &&
+ !intersect.object.name.includes("MeasurementReference") &&
+ !intersect.object.name.includes("commentHolder") &&
+ !intersect.object.name.includes("agv-collider") &&
+ !(intersect.object.type === "GridHelper")
+ );
+
+ if (intersects.length > 0) {
+ const point = intersects[0].point;
+ setHoverPos(new Vector3(point.x, Math.max(point.y, 0), point.z));
+ } else {
+ setHoverPos(null);
+ }
+ };
+
+ const onMouseClick = () => {
+ if (drag) return;
+
+ const intersects = raycaster
+ .intersectObjects(scene.children, true)
+ .filter(
+ (intersect) =>
+ !intersect.object.name.includes("Roof") &&
+ !intersect.object.name.includes("MeasurementReference") &&
+ !intersect.object.name.includes("commentHolder") &&
+ !intersect.object.name.includes("agv-collider") &&
+ !(intersect.object.type === "GridHelper")
+ );
+ if (intersects.length > 0) {
+ const position = new Vector3(intersects[0].point.x, Math.max(intersects[0].point.y, 0), intersects[0].point.z);
+
+ const comment: CommentSchema = {
+ state: 'active',
+ commentId: MathUtils.generateUUID(),
+ creatorId: userId,
+ createdAt: new Date().toISOString(),
+ comment: '',
+ lastUpdatedAt: new Date().toISOString(),
+ position: position.toArray(),
+ rotation: [0, 0, 0],
+ replies: []
+ }
+
+ addComment(comment);
+ setHoverPos(null);
+ }
+ }
+
+ if (activeTool === 'comment') {
+ canvasElement.addEventListener("mousedown", onMouseDown);
+ canvasElement.addEventListener("mouseup", onMouseUp);
+ canvasElement.addEventListener("mousemove", onMouseMove);
+ canvasElement.addEventListener("click", onMouseClick);
+ } else {
+ setHoverPos(null);
+ }
+
+ return () => {
+ canvasElement.removeEventListener("mousedown", onMouseDown);
+ canvasElement.removeEventListener("mouseup", onMouseUp);
+ canvasElement.removeEventListener("mousemove", onMouseMove);
+ canvasElement.removeEventListener("click", onMouseClick);
+ };
+ }, [activeTool, camera])
+
+ return (
+ <>
+
+
+ {hoverPos && (
+
+
+
+ )}
+ >
+ )
+}
+
+export default CommentsGroup
\ No newline at end of file
diff --git a/app/src/modules/collaboration/comments/instances/commentInstance/commentInstance.tsx b/app/src/modules/collaboration/comments/instances/commentInstance/commentInstance.tsx
new file mode 100644
index 0000000..5b5c145
--- /dev/null
+++ b/app/src/modules/collaboration/comments/instances/commentInstance/commentInstance.tsx
@@ -0,0 +1,74 @@
+import { Html, TransformControls } from '@react-three/drei';
+import { useEffect, useRef, useState } from 'react'
+import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
+import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
+import CommentThreads from '../../../../../components/ui/collaboration/CommentThreads';
+
+function CommentInstance({ comment }: { comment: CommentSchema }) {
+ const { isPlaying } = usePlayButtonStore();
+ const CommentRef = useRef(null);
+ const [selectedComment, setSelectedComment] = useState(null);
+ const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
+
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ const keyCombination = detectModifierKeys(e);
+ if (!selectedComment) return;
+ if (keyCombination === "G") {
+ setTransformMode((prev) => (prev === "translate" ? null : "translate"));
+ }
+ if (keyCombination === "R") {
+ setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [selectedComment]);
+
+ if (comment.state === 'inactive' || isPlaying) return null;
+
+ return (
+
+ <>
+
+
+ {/* {
+ e.stopPropagation();
+ setSelectedComment(comment);
+ console.log("down");
+ }}
+ onPointerOver={(e) => {
+ e.stopPropagation();
+ }}>
+
+ hii
+
+
*/}
+
+ {CommentRef.current && transformMode && (
+ {
+
+ }}
+ />
+ )}
+ >
+ )
+}
+
+export default CommentInstance;
\ No newline at end of file
diff --git a/app/src/modules/collaboration/comments/instances/commentInstances.tsx b/app/src/modules/collaboration/comments/instances/commentInstances.tsx
new file mode 100644
index 0000000..53dc4c6
--- /dev/null
+++ b/app/src/modules/collaboration/comments/instances/commentInstances.tsx
@@ -0,0 +1,23 @@
+import React, { useEffect } from 'react'
+import CommentInstance from './commentInstance/commentInstance'
+import { useCommentStore } from '../../../../store/collaboration/useCommentStore'
+
+function CommentInstances() {
+ const { comments } = useCommentStore();
+
+ useEffect(() => {
+ console.log('comments: ', comments);
+ }, [comments])
+
+ return (
+ <>
+ {comments.map((comment: CommentSchema) => (
+
+
+
+ ))}
+ >
+ )
+}
+
+export default CommentInstances
\ No newline at end of file
diff --git a/app/src/modules/simulation/events/arrows/arrows.tsx b/app/src/modules/simulation/events/arrows/arrows.tsx
index 5eb6a9f..943b614 100644
--- a/app/src/modules/simulation/events/arrows/arrows.tsx
+++ b/app/src/modules/simulation/events/arrows/arrows.tsx
@@ -1,6 +1,7 @@
import * as THREE from "three";
-import { useMemo, useRef } from "react";
+import { useMemo, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
+import { useDeleteTool } from "../../../../store/builder/store";
interface ConnectionLine {
id: string;
@@ -10,8 +11,10 @@ interface ConnectionLine {
}
export function Arrows({ connections }: { connections: ConnectionLine[] }) {
+ const [hoveredLineKey, setHoveredLineKey] = useState(null);
const groupRef = useRef(null);
const { scene } = useThree();
+ const { deleteTool } = useDeleteTool();
const getWorldPositionFromScene = (uuid: string): THREE.Vector3 | null => {
const obj = scene.getObjectByProperty("uuid", uuid);
@@ -52,11 +55,25 @@ export function Arrows({ connections }: { connections: ConnectionLine[] }) {
return (
-
-
+ setHoveredLineKey(key)}
+ onPointerOut={() => setHoveredLineKey(null)}
+ >
+
-
-
+ setHoveredLineKey(key)}
+ onPointerOut={() => setHoveredLineKey(null)}
+ >
+
);
diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx
index 7861d30..7fac1df 100644
--- a/app/src/pages/Project.tsx
+++ b/app/src/pages/Project.tsx
@@ -21,7 +21,7 @@ import { usePlayButtonStore } from "../store/usePlayButtonStore";
import MarketPlace from "../modules/market/MarketPlace";
import LoadingPage from "../components/templates/LoadingPage";
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
-import { useSelectedUserStore } from "../store/useCollabStore";
+import { useSelectedUserStore } from "../store/collaboration/useCollabStore";
import FollowPerson from "../components/templates/FollowPerson";
import Scene from "../modules/scene/scene";
import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
@@ -34,7 +34,7 @@ import Footer from "../components/footer/Footer";
import SelectFloorPlan from "../components/temporary/SelectFloorPlan";
import ControlsPlayer from "../components/layout/controls/ControlsPlayer";
import CompareLayOut from "../components/ui/compareVersion/CompareLayOut";
-import {useToggleStore} from "../store/useUIToggleStore";
+import { useToggleStore } from "../store/useUIToggleStore";
import RegularDropDown from "../components/ui/inputs/RegularDropDown";
import VersionSaved from "../components/layout/sidebarRight/versionHisory/VersionSaved";
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
@@ -183,7 +183,6 @@ const Project: React.FC = () => {
>
)}
-
);
};
diff --git a/app/src/store/useCollabStore.ts b/app/src/store/collaboration/useCollabStore.ts
similarity index 95%
rename from app/src/store/useCollabStore.ts
rename to app/src/store/collaboration/useCollabStore.ts
index 499e857..b768126 100644
--- a/app/src/store/useCollabStore.ts
+++ b/app/src/store/collaboration/useCollabStore.ts
@@ -1,36 +1,36 @@
-import { create } from 'zustand';
-
-interface SelectedUser {
- color: string;
- name: string;
- id: string,
- location?: {
- position: {
- x: number;
- y: number;
- z: number;
- };
- rotation?: {
- x: number;
- y: number;
- z: number;
- };
- target?: {
- x: number;
- y: number;
- z: number;
- };
- }
-}
-
-interface SelectedUserStore {
- selectedUser: SelectedUser | null;
- setSelectedUser: (user: SelectedUser) => void;
- clearSelectedUser: () => void;
-}
-
-export const useSelectedUserStore = create
((set) => ({
- selectedUser: null,
- setSelectedUser: (user) => set({ selectedUser: user }),
- clearSelectedUser: () => set({ selectedUser: null }),
-}));
+import { create } from 'zustand';
+
+interface SelectedUser {
+ color: string;
+ name: string;
+ id: string,
+ location?: {
+ position: {
+ x: number;
+ y: number;
+ z: number;
+ };
+ rotation?: {
+ x: number;
+ y: number;
+ z: number;
+ };
+ target?: {
+ x: number;
+ y: number;
+ z: number;
+ };
+ }
+}
+
+interface SelectedUserStore {
+ selectedUser: SelectedUser | null;
+ setSelectedUser: (user: SelectedUser) => void;
+ clearSelectedUser: () => void;
+}
+
+export const useSelectedUserStore = create((set) => ({
+ selectedUser: null,
+ setSelectedUser: (user) => set({ selectedUser: user }),
+ clearSelectedUser: () => set({ selectedUser: null }),
+}));
diff --git a/app/src/store/collaboration/useCommentStore.ts b/app/src/store/collaboration/useCommentStore.ts
new file mode 100644
index 0000000..cd4d7c3
--- /dev/null
+++ b/app/src/store/collaboration/useCommentStore.ts
@@ -0,0 +1,92 @@
+import { create } from 'zustand';
+import { immer } from 'zustand/middleware/immer';
+
+interface CommentStore {
+ comments: CommentsSchema;
+
+ // Comment operations
+ addComment: (comment: CommentSchema) => void;
+ setComments: (comments: CommentsSchema) => void;
+ updateComment: (commentId: string, updates: Partial) => void;
+ removeComment: (commentId: string) => void;
+
+ // Reply operations
+ addReply: (commentId: string, reply: Reply) => void;
+ updateReply: (commentId: string, replyId: string, updates: Partial) => void;
+ removeReply: (commentId: string, replyId: string) => void;
+
+ // Getters
+ getCommentById: (commentId: string) => CommentSchema | undefined;
+}
+
+export const useCommentStore = create()(
+ immer((set, get) => ({
+ comments: [],
+
+ // Comment operations
+ addComment: (comment) => {
+ set((state) => {
+ if (!state.comments.find(c => c.commentId === comment.commentId)) {
+ state.comments.push(JSON.parse(JSON.stringify(comment)));
+ }
+ });
+ },
+
+ setComments: (comments) => {
+ set((state) => {
+ state.comments = comments;
+ });
+ },
+
+ updateComment: (commentId, updates) => {
+ set((state) => {
+ const comment = state.comments.find(c => c.commentId === commentId);
+ if (comment) {
+ Object.assign(comment, updates);
+ }
+ });
+ },
+
+ removeComment: (commentId) => {
+ set((state) => {
+ state.comments = state.comments.filter(c => c.commentId !== commentId);
+ });
+ },
+
+ // Reply operations
+ addReply: (commentId, reply) => {
+ set((state) => {
+ const comment = state.comments.find(c => c.commentId === commentId);
+ if (comment) {
+ comment.replies.push(reply);
+ }
+ });
+ },
+
+ updateReply: (commentId, replyId, updates) => {
+ set((state) => {
+ const comment = state.comments.find(c => c.commentId === commentId);
+ if (comment) {
+ const reply = comment.replies.find(r => r.replyId === replyId);
+ if (reply) {
+ Object.assign(reply, updates);
+ }
+ }
+ });
+ },
+
+ removeReply: (commentId, replyId) => {
+ set((state) => {
+ const comment = state.comments.find(c => c.commentId === commentId);
+ if (comment) {
+ comment.replies = comment.replies.filter(r => r.replyId !== replyId);
+ }
+ });
+ },
+
+ // Getter
+ getCommentById: (commentId) => {
+ return get().comments.find(c => c.commentId === commentId);
+ },
+ }))
+);
diff --git a/app/src/styles/scene/comments.scss b/app/src/styles/scene/comments.scss
index 39fc15c..7c54466 100644
--- a/app/src/styles/scene/comments.scss
+++ b/app/src/styles/scene/comments.scss
@@ -1,10 +1,13 @@
@use "../abstracts/variables" as *;
@use "../abstracts/mixins" as *;
+.comments-main-wrapper{
+ position: relative;
+}
.comments-threads-wrapper {
- position: fixed;
- top: 50%;
- left: 50%;
+ position: absolute;
+ top: 0;
+ left: 0;
padding: 4px;
background: var(--background-color);
border-radius: #{$border-radius-large} #{$border-radius-large} #{$border-radius-large} 0;
@@ -35,9 +38,6 @@
display: flex;
align-items: start;
flex-direction: column;
- // padding-top: 0;
- // height: 0;
- // width: 0;
overflow: hidden;
transition: all 0.2s;
@@ -60,16 +60,15 @@
}
.replies {
- margin-top: 10px;
+ margin-top: 4px;
font-size: var(--font-size-small);
color: var(--input-text-color);
}
-
.header,
.message,
.replies {
- opacity: 0;
display: none;
+ opacity: 0;
}
}
@@ -77,6 +76,7 @@
min-width: 200px;
max-width: 260px;
padding: 12px;
+ padding-top: 0;
height: 100%;
}
}
@@ -85,13 +85,11 @@
.users-commented {
padding: 12px;
}
-
-
.header,
.message,
.replies {
- opacity: 1 !important;
display: flex !important;
+ opacity: 1 !important;
}
}
}
\ No newline at end of file
diff --git a/app/src/types/collaborationTypes.d.ts b/app/src/types/collaborationTypes.d.ts
new file mode 100644
index 0000000..445949a
--- /dev/null
+++ b/app/src/types/collaborationTypes.d.ts
@@ -0,0 +1,21 @@
+interface CommentSchema {
+ state: "active" | "inactive";
+ commentId: string;
+ creatorId: string;
+ createdAt: string;
+ comment: string;
+ lastUpdatedAt: string;
+ position: [number, number, number];
+ rotation: [number, number, number];
+ replies: Reply[];
+}
+
+interface Reply {
+ replyId: string;
+ creatorId: string;
+ createdAt: string;
+ lastUpdatedAt: string;
+ reply: string;
+}
+
+type CommentsSchema = CommentSchema[];
\ No newline at end of file