diff --git a/client/src/config/api.js b/client/src/config/api.js
new file mode 100644
index 0000000..5d3a7ad
--- /dev/null
+++ b/client/src/config/api.js
@@ -0,0 +1,20 @@
+const getActiveServer = () => {
+ const saved = localStorage.getItem("activeServer");
+ if (!saved) return null;
+ try {
+ return JSON.parse(saved);
+ } catch (e) {
+ return null;
+ }
+};
+
+export const getServerUrl = () => {
+ const server = getActiveServer();
+ return server ? server.connectUrl : null;
+};
+
+export const config = {
+ get serverUrl() {
+ return getServerUrl();
+ },
+};
diff --git a/client/src/views/GameInterface/GameInterface.jsx b/client/src/views/GameInterface/GameInterface.jsx
index 393603c..a13f492 100644
--- a/client/src/views/GameInterface/GameInterface.jsx
+++ b/client/src/views/GameInterface/GameInterface.jsx
@@ -12,27 +12,25 @@ import BaseTab from "./tabs/BaseTab";
import QuestsTab from "./tabs/QuestsTab";
import ShopTab from "./tabs/ShopTab";
import CraftingTab from "./tabs/CraftingTab";
-
+import ChatTab from "./tabs/ChatTab";
+import ItemListTab from "./tabs/ItemListTab.jsx";
+import Notification from "./tabs/NotificationTab.jsx";
import DungeonScreen from "./components/DungeonScreen";
import { useSocket } from "../../hooks/useSocket.js";
-import ItemListTab from "./tabs/ItemListTab.jsx";
const GameInterface = ({ onExit }) => {
const [activeTab, setActiveTab] = useState("dashboard");
const [activeDungeonSession, setActiveDungeonSession] = useState(null);
- const [onlinePlayers, setOnlinePlayers] = useState([]);
const { socket } = useSocket();
useEffect(() => {
if (!socket) return;
socket.on("dungeon:started", (sessionData) => {
- console.log("Deployment initiated:", sessionData);
setActiveDungeonSession(sessionData);
});
socket.on("dungeon:completed", (results) => {
- console.log("Mission accomplished:", results);
setActiveDungeonSession(null);
});
@@ -81,18 +79,17 @@ const GameInterface = ({ onExit }) => {
shop: ,
crafting: ,
itemlist: ,
+ chat: ,
+ notifications: ,
};
return (
-
-
{tabs[activeTab] || }
-
);
diff --git a/client/src/views/GameInterface/components/Navigation.css b/client/src/views/GameInterface/components/Navigation.css
index c0067e6..c3517eb 100644
--- a/client/src/views/GameInterface/components/Navigation.css
+++ b/client/src/views/GameInterface/components/Navigation.css
@@ -1,5 +1,4 @@
.main-nav {
- /* Зменшуємо загальну висоту з 60px до 45px */
height: 45px;
background: #0a0f18;
border-bottom: 1px solid #1a2638;
@@ -19,7 +18,7 @@
.nav-container {
display: flex;
padding: 0 5px;
- gap: 2px; /* Мінімальний зазор між кнопками */
+ gap: 2px;
}
.nav-btn {
@@ -27,7 +26,6 @@
flex-direction: column;
justify-content: center;
align-items: center;
- /* Зменшуємо горизонтальні відступи */
padding: 0 12px;
background: transparent;
border: none;
@@ -42,21 +40,20 @@
.nav-btn-content {
display: flex;
align-items: center;
- gap: 6px; /* Менша відстань між іконкою та текстом */
+ gap: 6px;
}
.nav-btn i {
- font-size: 0.9rem; /* Зменшили розмір іконок */
+ font-size: 0.9rem;
}
.nav-label {
- font-size: 9px; /* Ультра-компактний шрифт */
+ font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
-/* Активний стан */
.nav-btn.active {
color: #00d4ff;
background: linear-gradient(to bottom, rgba(0, 212, 255, 0.08), transparent);
@@ -67,7 +64,7 @@
bottom: 0;
left: 0;
right: 0;
- height: 2px; /* Тонша лінія */
+ height: 2px;
background: #00d4ff;
box-shadow: 0 0 8px #00d4ff;
transform: scaleX(0);
@@ -78,21 +75,66 @@
transform: scaleX(1);
}
-/* Адаптивність для мобілок (ще компактніше) */
@media (max-width: 768px) {
.main-nav {
- height: 42px; /* Мінімум для зручного натискання пальцем */
+ height: 42px;
}
.nav-label {
- display: none; /* Тільки іконки на мобілках */
+ display: none;
}
.nav-btn {
- padding: 0 18px; /* Більше місця для пальця, але без тексту */
+ padding: 0 18px;
}
.nav-btn i {
font-size: 1.1rem;
}
}
+
+.icon-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.nav-badge {
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ background: #ff3e3e;
+ color: white;
+ font-size: 10px;
+ font-weight: bold;
+ min-width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px;
+ box-shadow: 0 0 10px rgba(255, 62, 62, 0.5);
+ border: 1px solid #1a1a1a;
+ animation: pulse-red 2s infinite;
+}
+
+@keyframes pulse-red {
+ 0% {
+ transform: scale(1);
+ box-shadow: 0 0 0 0 rgba(255, 62, 62, 0.7);
+ }
+ 70% {
+ transform: scale(1.1);
+ box-shadow: 0 0 0 5px rgba(255, 62, 62, 0);
+ }
+ 100% {
+ transform: scale(1);
+ box-shadow: 0 0 0 0 rgba(255, 62, 62, 0);
+ }
+}
+
+.nav-btn.notifications.active i {
+ color: #ffd700;
+}
diff --git a/client/src/views/GameInterface/components/Navigation.jsx b/client/src/views/GameInterface/components/Navigation.jsx
index e319d13..8a1863b 100644
--- a/client/src/views/GameInterface/components/Navigation.jsx
+++ b/client/src/views/GameInterface/components/Navigation.jsx
@@ -1,8 +1,12 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
import GameDataManager from "../../../services/GameDataManager.js";
+import { useSocket } from "../../../hooks/useSocket";
import "./Navigation.css";
const Navigation = ({ activeTab, onTabChange }) => {
+ const { socket } = useSocket();
+ const [unreadCount, setUnreadCount] = useState(0);
+
const tabs = [
{ id: "dashboard", icon: "fa-tachometer-alt" },
{ id: "dungeons", icon: "fa-dungeon" },
@@ -11,10 +15,40 @@ const Navigation = ({ activeTab, onTabChange }) => {
{ id: "shop", icon: "fa-store" },
{ id: "crafting", icon: "fa-hammer" },
{ id: "itemlist", icon: "fa-list-ul" },
+ { id: "chat", icon: "fa-comments" },
+ { id: "notifications", icon: "fa-bell" },
];
+ useEffect(() => {
+ if (!socket) return;
+
+ const handleNotifyUpdate = (count) => {
+ setUnreadCount(count);
+ };
+
+ socket.on("notifications:unread_count", handleNotifyUpdate);
+
+ socket.on("notification:new", () => {
+ if (activeTab !== "notifications") {
+ setUnreadCount((prev) => prev + 1);
+ }
+ });
+
+ return () => {
+ socket.off("notifications:unread_count", handleNotifyUpdate);
+ socket.off("notification:new");
+ };
+ }, [socket, activeTab]);
+
+ const handleTabClick = (id) => {
+ if (id === "notifications") setUnreadCount(0);
+ onTabChange(id);
+ };
+
const getLabel = (id) => {
if (id === "itemlist") return "ITEM_LIST";
+ if (id === "chat") return "CHAT";
+ if (id === "notifications") return "ALERTS";
return GameDataManager.t(`category.tabs.original.${id}`);
};
@@ -24,11 +58,16 @@ const Navigation = ({ activeTab, onTabChange }) => {
{tabs.map((tab) => (