diff --git a/gurubu-client/src/app/components/room/retro-board/RetroColumn.tsx b/gurubu-client/src/app/components/room/retro-board/RetroColumn.tsx
index 297f225..8c172f9 100644
--- a/gurubu-client/src/app/components/room/retro-board/RetroColumn.tsx
+++ b/gurubu-client/src/app/components/room/retro-board/RetroColumn.tsx
@@ -356,6 +356,7 @@ const RetroColumn = ({
onColumnHeaderImageUpload(columnKey, e)}
style={{ display: "none" }}
/>
diff --git a/gurubu-client/src/app/components/room/retro-board/RetroErrorPopup.tsx b/gurubu-client/src/app/components/room/retro-board/RetroErrorPopup.tsx
index 2b226ad..d6051d7 100644
--- a/gurubu-client/src/app/components/room/retro-board/RetroErrorPopup.tsx
+++ b/gurubu-client/src/app/components/room/retro-board/RetroErrorPopup.tsx
@@ -21,6 +21,9 @@ const RetroErrorPopup = ({ title, retroId }: IProps) => {
const lobby = getCurrentRetroLobby(retroId);
if (retroNickname && lobby) {
+ if (!socket.connected) {
+ socket.connect();
+ }
socket.emit("joinRetro", {
nickname: retroNickname,
retroId: retroId,
diff --git a/gurubu-client/src/app/components/room/retro-board/hooks/useBoardImages.ts b/gurubu-client/src/app/components/room/retro-board/hooks/useBoardImages.ts
index b53679a..79cf09d 100644
--- a/gurubu-client/src/app/components/room/retro-board/hooks/useBoardImages.ts
+++ b/gurubu-client/src/app/components/room/retro-board/hooks/useBoardImages.ts
@@ -1,6 +1,10 @@
"use client";
import { useState, useEffect } from "react";
+import toast from "react-hot-toast";
+
+const MAX_HEADER_IMAGE_SIZE_BYTES = 1024 * 1024; // 1MB
+const MAX_HEADER_IMAGE_DIMENSION = 1200;
interface UseBoardImagesParams {
socket: any;
@@ -152,36 +156,54 @@ export const useBoardImages = ({
const file = e.target.files?.[0];
if (!file) return;
- const reader = new FileReader();
- reader.onload = (event) => {
- const result = event.target?.result as string;
- const img = new Image();
- img.onload = () => {
- const maxSize = 300;
- let width = img.naturalWidth;
- let height = img.naturalHeight;
- if (width > maxSize || height > maxSize) {
- const ratio = Math.min(maxSize / width, maxSize / height);
- width = width * ratio;
- height = height * ratio;
- }
- const newImage = {
- id: Date.now().toString(),
- src: result,
- x: -99999,
- y: -99999,
- width,
- height
- };
- const updatedImages = [...boardImages, newImage];
- setBoardImages(updatedImages);
- if (userInfo.lobby) {
- socket.emit("updateBoardImages", roomId, updatedImages, userInfo.lobby.credentials);
+ try {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const result = event.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ try {
+ const maxSize = 300;
+ let width = img.naturalWidth;
+ let height = img.naturalHeight;
+ if (width > maxSize || height > maxSize) {
+ const ratio = Math.min(maxSize / width, maxSize / height);
+ width = width * ratio;
+ height = height * ratio;
+ }
+ const newImage = {
+ id: Date.now().toString(),
+ src: result,
+ x: -99999,
+ y: -99999,
+ width,
+ height
+ };
+ const updatedImages = [...boardImages, newImage];
+ setBoardImages(updatedImages);
+ if (userInfo.lobby) {
+ socket.emit("updateBoardImages", roomId, updatedImages, userInfo.lobby.credentials);
+ }
+ } catch (err) {
+ console.error("Board image process error:", err);
+ toast.error("Failed to process image");
+ }
+ };
+ img.onerror = () => toast.error("Invalid image file");
+ img.src = result;
+ } catch (err) {
+ console.error("Board image load error:", err);
+ toast.error("Failed to load image");
}
};
- img.src = result;
- };
- reader.readAsDataURL(file);
+ reader.onerror = () => toast.error("Failed to read file");
+ reader.readAsDataURL(file);
+ } catch (err) {
+ console.error("Board image upload error:", err);
+ toast.error("Failed to upload image");
+ }
+ e.target.value = "";
};
const handleImageDragStart = (image: any, e: React.DragEvent) => {
@@ -213,16 +235,77 @@ export const useBoardImages = ({
const handleColumnHeaderImageUpload = (columnKey: string, e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (!file) return;
- const reader = new FileReader();
- reader.onload = (event) => {
- const result = event.target?.result as string;
- const updatedImages = { ...columnHeaderImages, [columnKey]: result };
- setColumnHeaderImages(updatedImages);
- if (userInfo.lobby) {
- socket.emit("updateColumnHeaderImages", roomId, updatedImages, userInfo.lobby.credentials);
- }
- };
- reader.readAsDataURL(file);
+
+ // Tek image: zaten varsa yükleme (UI'da gizli ama yine de guard)
+ if (columnHeaderImages[columnKey]) {
+ toast.error("Only one image per column. Remove the current image first.");
+ return;
+ }
+
+ if (file.size > MAX_HEADER_IMAGE_SIZE_BYTES) {
+ toast.error("Image must be under 1MB");
+ return;
+ }
+
+ try {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const result = event.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ try {
+ let width = img.naturalWidth;
+ let height = img.naturalHeight;
+ if (width > MAX_HEADER_IMAGE_DIMENSION || height > MAX_HEADER_IMAGE_DIMENSION) {
+ const ratio = Math.min(MAX_HEADER_IMAGE_DIMENSION / width, MAX_HEADER_IMAGE_DIMENSION / height);
+ width = Math.round(width * ratio);
+ height = Math.round(height * ratio);
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext("2d");
+ if (ctx) {
+ ctx.drawImage(img, 0, 0, width, height);
+ const resizedBase64 = canvas.toDataURL("image/jpeg", 0.85);
+ const updatedImages = { ...columnHeaderImages, [columnKey]: resizedBase64 };
+ setColumnHeaderImages(updatedImages);
+ if (userInfo.lobby) {
+ socket.emit("updateColumnHeaderImages", roomId, updatedImages, userInfo.lobby.credentials);
+ }
+ } else {
+ const updatedImages = { ...columnHeaderImages, [columnKey]: result };
+ setColumnHeaderImages(updatedImages);
+ if (userInfo.lobby) {
+ socket.emit("updateColumnHeaderImages", roomId, updatedImages, userInfo.lobby.credentials);
+ }
+ }
+ } else {
+ const updatedImages = { ...columnHeaderImages, [columnKey]: result };
+ setColumnHeaderImages(updatedImages);
+ if (userInfo.lobby) {
+ socket.emit("updateColumnHeaderImages", roomId, updatedImages, userInfo.lobby.credentials);
+ }
+ }
+ } catch (err) {
+ console.error("Header image process error:", err);
+ toast.error("Failed to process image");
+ }
+ };
+ img.onerror = () => toast.error("Invalid image file");
+ img.src = result;
+ } catch (err) {
+ console.error("Header image load error:", err);
+ toast.error("Failed to load image");
+ }
+ };
+ reader.onerror = () => toast.error("Failed to read file");
+ reader.readAsDataURL(file);
+ } catch (err) {
+ console.error("Header image upload error:", err);
+ toast.error("Failed to upload image");
+ }
+ e.target.value = "";
};
const handleRemoveColumnHeaderImage = (columnKey: string) => {
diff --git a/gurubu-client/src/app/components/room/retro-board/hooks/useRetroSocketEvents.ts b/gurubu-client/src/app/components/room/retro-board/hooks/useRetroSocketEvents.ts
index 5e47561..e2ed10d 100644
--- a/gurubu-client/src/app/components/room/retro-board/hooks/useRetroSocketEvents.ts
+++ b/gurubu-client/src/app/components/room/retro-board/hooks/useRetroSocketEvents.ts
@@ -206,6 +206,11 @@ export const useRetroSocketEvents = ({
socket.on("emoteReaction", handleEmoteReaction);
socket.on("retroEnded", handleRetroEnded);
+ const handleDisconnect = () => {
+ setShowErrorPopup(true);
+ };
+ socket.on("disconnect", handleDisconnect);
+
return () => {
clearInterval(heartbeatInterval);
document.removeEventListener('visibilitychange', handleVisibilityChange);
@@ -235,6 +240,7 @@ export const useRetroSocketEvents = ({
socket.off("cardsImported", handleCardsImported);
socket.off("emoteReaction", handleEmoteReaction);
socket.off("retroEnded", handleRetroEnded);
+ socket.off("disconnect", handleDisconnect);
};
}, [socket, roomId, avatarSeed]);
};
diff --git a/gurubu-client/src/app/styles/room/retro-board/retro-board.scss b/gurubu-client/src/app/styles/room/retro-board/retro-board.scss
index 75a7ce9..4884c8b 100644
--- a/gurubu-client/src/app/styles/room/retro-board/retro-board.scss
+++ b/gurubu-client/src/app/styles/room/retro-board/retro-board.scss
@@ -2149,7 +2149,7 @@
&__header-image {
position: relative;
width: 100%;
- height: 120px;
+ height: 200px;
margin-bottom: 1rem;
overflow: hidden;
border-radius: 8px;