diff --git a/frontend/src/auth/api.ts b/frontend/src/auth/api.ts new file mode 100644 index 0000000..692c3b5 --- /dev/null +++ b/frontend/src/auth/api.ts @@ -0,0 +1,72 @@ +import { API_BASE_URL } from "../core/config"; +import { showLogin } from "../navigation"; +import type { Headers, User } from "../core/types"; +import { clearAlerts } from "./auth"; + +/** + * Current authenticated user information + * @type {User | null} + */ +export let currentUser: User | null = null; + +/** + * JWT authentication token + * @type {string | null} + */ +export let authToken: string | null = null; + +/** + * Sets the current user to the values provided. + * @param token The authentication JWT token. + * @param user The current authenticated user. + */ +export function setUser(token: string, user: User) { + authToken = token + currentUser = user +} + +/** + * Generates authentication headers for API requests + * @param {boolean} json - Whether to include JSON content type header + * @returns {Headers} Headers object with authentication and content type + */ +export function getAuthHeaders(json: boolean = true): Headers { + const headers: Headers = {}; + + if (json) { + headers["Content-Type"] = "application/json"; + } + + if (authToken) { + headers['Authorization'] = `Bearer ${authToken}`; + } + return headers; +} + +/** + * Checks authentication status on page load + */ +export async function checkAuthStatus(): Promise { + // For JWT, we don't have a persistent token on page load + // So we'll just show the login form + showLogin(); +} + +/** + * Logs out the current user and clears session data + */ +export async function logout(): Promise { + try { + await fetch(`${API_BASE_URL}/logout`, { + method: 'GET', + headers: getAuthHeaders() + }); + } catch (error) { + console.error('Logout error:', error); + } + + currentUser = null; + authToken = null; + showLogin(); + clearAlerts(); +} \ No newline at end of file diff --git a/frontend/src/auth.ts b/frontend/src/auth/auth.ts similarity index 57% rename from frontend/src/auth.ts rename to frontend/src/auth/auth.ts index 10e79b8..4dd35e0 100644 --- a/frontend/src/auth.ts +++ b/frontend/src/auth/auth.ts @@ -5,90 +5,14 @@ * @version 1.0.0 */ -import { loadMessages } from "./chat"; -import { initializeProfile } from "./profile"; -import type { Headers, ErrorResponse, User, LoginResponse, LoginRequest, RegisterRequest } from "./types"; -import { API_BASE_URL } from "./config"; - -/** - * Current authenticated user information - * @type {User | null} - */ -export let currentUser: User | null = null; - -/** - * JWT authentication token - * @type {string | null} - */ -export let authToken: string | null = null; - -/** - * Generates authentication headers for API requests - * @param {boolean} json - Whether to include JSON content type header - * @returns {Headers} Headers object with authentication and content type - * @function getAuthHeaders - * @example - * const headers = getAuthHeaders(); - * fetch('/api/endpoint', { headers }); - */ -export function getAuthHeaders(json: boolean = true): Headers { - const headers: Headers = {}; - - if (json) { - headers["Content-Type"] = "application/json"; - } - - if (authToken) { - headers['Authorization'] = `Bearer ${authToken}`; - } - return headers; -} - -/** - * Shows the login form and hides other interfaces - * @function showLogin - * @example - * showLogin(); - */ -export function showLogin(): void { - document.getElementById('login-form')!.style.display = 'flex'; - document.getElementById('register-form')!.style.display = 'none'; - document.getElementById('chat-interface')!.style.display = 'none'; - clearAlerts(); - document.getElementById("electron-title-bar")!.classList.add("color-surface"); -} - -/** - * Shows the registration form and hides other interfaces - * @function showRegister - * @example - * showRegister(); - */ -export function showRegister(): void { - document.getElementById('login-form')!.style.display = 'none'; - document.getElementById('register-form')!.style.display = 'flex'; - document.getElementById('chat-interface')!.style.display = 'none'; - clearAlerts(); - document.getElementById("electron-title-bar")!.classList.add("color-surface"); -} - -/** - * Shows the chat interface and hides authentication forms - * @function showChat - * @example - * showChat(); - */ -export function showChat(): void { - document.getElementById('login-form')!.style.display = 'none'; - document.getElementById('register-form')!.style.display = 'none'; - document.getElementById('chat-interface')!.style.display = 'block'; - loadMessages(); - document.getElementById("electron-title-bar")!.classList.remove("color-surface"); -} +import { initializeProfile } from "../userPanel/profile/profile"; +import type { ErrorResponse, LoginResponse, LoginRequest, RegisterRequest } from "../core/types"; +import { API_BASE_URL } from "../core/config"; +import { loadChat, showLogin, showRegister } from "../navigation"; +import { setUser } from "./api"; /** * Clears all alert messages from authentication forms - * @function clearAlerts * @private */ export function clearAlerts(): void { @@ -101,9 +25,6 @@ export function clearAlerts(): void { * @param {string} containerId - ID of the container to show the alert in * @param {string} message - Alert message to display * @param {'success' | 'danger'} type - Type of alert (success or danger) - * @function showAlert - * @example - * showAlert('login-alerts', 'Login successful!', 'success'); */ export function showAlert(containerId: string, message: string, type: "success" | "danger" = 'danger'): void { const container = document.getElementById(containerId)!; @@ -115,10 +36,7 @@ export function showAlert(containerId: string, message: string, type: "success" /** * Handles login form submission - * @async - * @function handleLogin * @param {Event} e - Form submission event - * @private */ async function handleLogin(e: Event): Promise { e.preventDefault(); @@ -151,10 +69,8 @@ async function handleLogin(e: Event): Promise { if (response.ok) { const data: LoginResponse = await response.json(); // Store the JWT token - authToken = data.token; - currentUser = data.user; - showChat(); - loadMessages(); // Start loading messages + setUser(data.token, data.user) + loadChat(); initializeProfile(); // Initialize profile after login } else { const data: ErrorResponse = await response.json(); @@ -167,8 +83,6 @@ async function handleLogin(e: Event): Promise { /** * Handles registration form submission - * @async - * @function handleRegister * @param {Event} e - Form submission event * @private */ @@ -234,72 +148,15 @@ async function handleRegister(e: Event): Promise { } /** - * Logs out the current user and clears session data - * @async - * @function logout - * @example - * await logout(); - */ -export async function logout(): Promise { - try { - await fetch(`${API_BASE_URL}/logout`, { - method: 'GET', - headers: getAuthHeaders() - }); - } catch (error) { - console.error('Logout error:', error); - } - - currentUser = null; - authToken = null; - showLogin(); - clearAlerts(); -} - -/** - * Loads the chat interface and initializes messaging - * @function loadChat - * @example - * loadChat(); - */ -export function loadChat(): void { - showChat(); - loadMessages(); -} - -/** - * Checks authentication status on page load - * @async - * @function checkAuthStatus - * @example - * await checkAuthStatus(); - */ -export async function checkAuthStatus(): Promise { - // For JWT, we don't have a persistent token on page load - // So we'll just show the login form - showLogin(); -} - -/** - * Sets up authentication form event listeners - * @function setupAuthForms + * Initializes authentication functionality * @private */ -function setupAuthForms(): void { +function init(): void { document.getElementById('login-form-element')!.addEventListener('submit', handleLogin); document.getElementById('register-form-element')!.addEventListener('submit', handleRegister); -} -/** - * Initializes links - * @function setupLinks - * @private - */ -function setupLinks(): void { document.getElementById("login-link")!.addEventListener("click", showLogin); document.getElementById("register-link")!.addEventListener("click", showRegister); } -// Initialize authentication forms -setupAuthForms(); -setupLinks(); \ No newline at end of file +init(); \ No newline at end of file diff --git a/frontend/src/chat.ts b/frontend/src/chat/chat.ts similarity index 91% rename from frontend/src/chat.ts rename to frontend/src/chat/chat.ts index 7cede95..120328d 100644 --- a/frontend/src/chat.ts +++ b/frontend/src/chat/chat.ts @@ -5,22 +5,19 @@ * @version 1.0.0 */ -import { getAuthHeaders, currentUser, authToken } from "./auth"; -import { API_BASE_URL } from "./config"; -import { websocket } from "./websocket"; -import type { Message, Messages, WebSocketMessage } from "./types"; -import { formatTime } from "./utils/utils"; -import { show as showContextMenu } from "./message-context-menu"; -import { show as showUserProfileDialog } from "./user-profile-dialog"; -import defaultAvatar from "./images/default-avatar.png"; +import { API_BASE_URL } from "../core/config"; +import { websocket } from "../websocket"; +import type { Message, Messages, WebSocketMessage } from "../core/types"; +import { formatTime } from "../utils/utils"; +import { show as showContextMenu } from "./contextMenu"; +import { show as showUserProfileDialog } from "./profileDialog"; +import defaultAvatar from "../resources/images/default-avatar.png"; +import { authToken, currentUser, getAuthHeaders } from "../auth/api"; /** * Adds a new message to the chat interface * @param {Message} message - Message object to display * @param {boolean} isAuthor - Whether the current user is the message author - * @function addMessage - * @example - * addMessage(messageData, messageData.username === currentUser.username); */ export function addMessage(message: Message, isAuthor: boolean): void { const messagesContainer = document.querySelector('.chat-messages') as HTMLElement; @@ -127,9 +124,6 @@ export function addMessage(message: Message, isAuthor: boolean): void { /** * Loads chat messages from the server - * @function loadMessages - * @example - * loadMessages(); */ export function loadMessages(): void { fetch(`${API_BASE_URL}/get_messages`, { @@ -158,9 +152,6 @@ export function loadMessages(): void { /** * Sends a message via WebSocket - * @function sendMessage - * @example - * sendMessage(); */ export function sendMessage(): void { const input = document.querySelector('.message-input') as HTMLInputElement; @@ -202,7 +193,6 @@ document.getElementById('message-form')!.addEventListener('submit', (e) => { /** * Updates an existing message in the chat interface * @param {Message} message - Updated message object - * @function updateMessage */ export function updateMessage(message: Message): void { const messageElement = document.querySelector(`[data-id="${message.id}"]`) as HTMLElement; @@ -227,7 +217,6 @@ export function updateMessage(message: Message): void { /** * Removes a message from the chat interface * @param {number} messageId - ID of the message to remove - * @function removeMessage */ export function removeMessage(messageId: number): void { const messageElement = document.querySelector(`[data-id="${messageId}"]`) as HTMLElement; @@ -239,7 +228,6 @@ export function removeMessage(messageId: number): void { /** * Handles WebSocket message updates * @param {WebSocketMessage} response - WebSocket response - * @function handleWebSocketMessage */ export function handleWebSocketMessage(response: WebSocketMessage): void { switch (response.type) { diff --git a/frontend/src/message-context-menu.ts b/frontend/src/chat/contextMenu.ts similarity index 96% rename from frontend/src/message-context-menu.ts rename to frontend/src/chat/contextMenu.ts index 60c0669..6705dc8 100644 --- a/frontend/src/message-context-menu.ts +++ b/frontend/src/chat/contextMenu.ts @@ -5,13 +5,13 @@ * @version 1.0.0 */ -import { currentUser, authToken } from "./auth"; -import { websocket } from "./websocket"; -import type { Message, WebSocketMessage } from "./types"; -import { showSuccess, showError } from "./utils/notification"; -import { delay } from "./utils/utils"; +import { websocket } from "../websocket"; +import type { Message, WebSocketMessage } from "../core/types"; +import { showSuccess, showError } from "../utils/notification"; +import { delay } from "../utils/utils"; import type { Dialog } from "mdui/components/dialog"; import type { TextField } from "mdui/components/text-field"; +import { currentUser, authToken } from "../auth/api"; let menu = document.getElementById("message-context-menu")!; @@ -83,7 +83,7 @@ export function show(message: Message, x: number, y: number): void { const deleteItem = menu.querySelector('[data-action="delete"]') as HTMLElement; const isAuthor = message.username === currentUser?.username; - const isOwner = !!currentUser?.admin; + const isOwner = currentUser?.admin; editItem.style.display = isAuthor ? 'flex' : 'none'; deleteItem.style.display = (isAuthor || isOwner) ? 'flex' : 'none'; diff --git a/frontend/src/user-profile-dialog.ts b/frontend/src/chat/profileDialog.ts similarity index 94% rename from frontend/src/user-profile-dialog.ts rename to frontend/src/chat/profileDialog.ts index 7900f2c..f980375 100644 --- a/frontend/src/user-profile-dialog.ts +++ b/frontend/src/chat/profileDialog.ts @@ -5,12 +5,12 @@ * @version 1.0.0 */ -import { getAuthHeaders, currentUser } from "./auth"; -import { API_BASE_URL } from "./config"; -import type { UserProfile } from "./types"; -import { showError, showSuccess } from "./utils/notification"; -import { formatTime } from "./utils/utils"; -import defaultAvatar from "./images/default-avatar.png"; +import { getAuthHeaders, currentUser } from "../auth/api"; +import { API_BASE_URL } from "../core/config"; +import type { UserProfile } from "../core/types"; +import { showError, showSuccess } from "../utils/notification"; +import { formatTime } from "../utils/utils"; +import defaultAvatar from "../resources/images/default-avatar.png"; let dialog = document.getElementById("user-profile-dialog")!; diff --git a/frontend/src/config.ts b/frontend/src/core/config.ts similarity index 66% rename from frontend/src/config.ts rename to frontend/src/core/config.ts index 2f7b204..9070347 100644 --- a/frontend/src/config.ts +++ b/frontend/src/core/config.ts @@ -7,21 +7,18 @@ /** * Base API endpoint for all backend requests - * @type {string} * @constant */ -export const API_BASE_URL: string = '/api'; +export const API_BASE_URL = `${location.host || "https://fromchat.toolbox-io.ru"}/api`; /** * Full API URL including hostname and port for WebSocket connections - * @type {string} * @constant */ -export const API_FULL_BASE_URL: string = `${location.host}/api`; +export const API_WS_BASE_URL = `${location.host || "fromchat.toolbox-io.ru"}/api`; /** * Application name displayed in UI and document title - * @type {string} * @constant */ -export const PRODUCT_NAME: string = "FromChat"; \ No newline at end of file +export const PRODUCT_NAME = "FromChat"; \ No newline at end of file diff --git a/frontend/src/init.ts b/frontend/src/core/init.ts similarity index 87% rename from frontend/src/init.ts rename to frontend/src/core/init.ts index 47d1fe1..41ca520 100644 --- a/frontend/src/init.ts +++ b/frontend/src/core/init.ts @@ -5,7 +5,7 @@ * @version 1.0.0 */ -import { showLogin } from "./auth"; +import { showLogin } from "../navigation"; import { PRODUCT_NAME } from "./config"; showLogin(); diff --git a/frontend/src/types.ts b/frontend/src/core/types.ts similarity index 100% rename from frontend/src/types.ts rename to frontend/src/core/types.ts diff --git a/frontend/src/electron/electron.ts b/frontend/src/electron/electron.ts index 948424a..92541e1 100644 --- a/frontend/src/electron/electron.ts +++ b/frontend/src/electron/electron.ts @@ -1,5 +1,12 @@ +/** + * @fileoverview Electron-specific code + * @description This module initializes Electron-specific functionality. + * @author denis0001-dev + * @version 1.0.0 + */ + import "../../electron.d.ts"; -import { PRODUCT_NAME } from "../config.ts"; +import { PRODUCT_NAME } from "../core/config.ts"; if (window.electronInterface !== undefined) { console.log("Running in Electron"); diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 13c0d55..9cda64c 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -5,15 +5,15 @@ * @version 1.0.0 */ -import './css/style.scss'; +import './resources/css/style.scss'; import "mdui/mdui.css"; import "./utils/material"; -import "./chat"; -import "./settings"; -import "./leftpanel"; -import "./init"; -import "./profile"; -import "./message-context-menu"; -import "./user-profile-dialog"; +import "./chat/chat"; +import "./userPanel/settings"; +import "./userPanel/leftpanel"; +import "./core/init"; +import "./userPanel/profile/profile"; +import "./chat/contextMenu"; +import "./chat/profileDialog"; import "./electron/electron"; \ No newline at end of file diff --git a/frontend/src/navigation.ts b/frontend/src/navigation.ts new file mode 100644 index 0000000..e6e5306 --- /dev/null +++ b/frontend/src/navigation.ts @@ -0,0 +1,43 @@ +import { clearAlerts } from "./auth/auth"; +import { loadMessages } from "./chat/chat"; + +/** + * Shows the login form and hides other interfaces. + */ +export function showLogin(): void { + document.getElementById('login-form')!.style.display = 'flex'; + document.getElementById('register-form')!.style.display = 'none'; + document.getElementById('chat-interface')!.style.display = 'none'; + clearAlerts(); + document.getElementById("electron-title-bar")!.classList.add("color-surface"); +} + +/** + * Shows the registration form and hides other interfaces. + */ +export function showRegister(): void { + document.getElementById('login-form')!.style.display = 'none'; + document.getElementById('register-form')!.style.display = 'flex'; + document.getElementById('chat-interface')!.style.display = 'none'; + clearAlerts(); + document.getElementById("electron-title-bar")!.classList.add("color-surface"); +} + +/** + * Shows the chat interface and hides authentication forms. + */ +export function showChat(): void { + document.getElementById('login-form')!.style.display = 'none'; + document.getElementById('register-form')!.style.display = 'none'; + document.getElementById('chat-interface')!.style.display = 'block'; + loadMessages(); + document.getElementById("electron-title-bar")!.classList.remove("color-surface"); +} + +/** + * Loads the chat interface and initializes messaging. + */ +export function loadChat(): void { + showChat(); + loadMessages(); +} \ No newline at end of file diff --git a/frontend/src/css/_auth.scss b/frontend/src/resources/css/_auth.scss similarity index 100% rename from frontend/src/css/_auth.scss rename to frontend/src/resources/css/_auth.scss diff --git a/frontend/src/css/_chat.scss b/frontend/src/resources/css/_chat.scss similarity index 100% rename from frontend/src/css/_chat.scss rename to frontend/src/resources/css/_chat.scss diff --git a/frontend/src/css/_electron.scss b/frontend/src/resources/css/_electron.scss similarity index 100% rename from frontend/src/css/_electron.scss rename to frontend/src/resources/css/_electron.scss diff --git a/frontend/src/css/_panelchat.scss b/frontend/src/resources/css/_panelchat.scss similarity index 100% rename from frontend/src/css/_panelchat.scss rename to frontend/src/resources/css/_panelchat.scss diff --git a/frontend/src/css/_profile.scss b/frontend/src/resources/css/_profile.scss similarity index 100% rename from frontend/src/css/_profile.scss rename to frontend/src/resources/css/_profile.scss diff --git a/frontend/src/css/_settings.scss b/frontend/src/resources/css/_settings.scss similarity index 100% rename from frontend/src/css/_settings.scss rename to frontend/src/resources/css/_settings.scss diff --git a/frontend/src/css/common/_animations.scss b/frontend/src/resources/css/common/_animations.scss similarity index 100% rename from frontend/src/css/common/_animations.scss rename to frontend/src/resources/css/common/_animations.scss diff --git a/frontend/src/css/common/_colors.scss b/frontend/src/resources/css/common/_colors.scss similarity index 100% rename from frontend/src/css/common/_colors.scss rename to frontend/src/resources/css/common/_colors.scss diff --git a/frontend/src/css/common/_components.scss b/frontend/src/resources/css/common/_components.scss similarity index 100% rename from frontend/src/css/common/_components.scss rename to frontend/src/resources/css/common/_components.scss diff --git a/frontend/src/css/common/_material.scss b/frontend/src/resources/css/common/_material.scss similarity index 100% rename from frontend/src/css/common/_material.scss rename to frontend/src/resources/css/common/_material.scss diff --git a/frontend/src/css/lib/fonts/material-symbols.scss b/frontend/src/resources/css/lib/fonts/material-symbols.scss similarity index 100% rename from frontend/src/css/lib/fonts/material-symbols.scss rename to frontend/src/resources/css/lib/fonts/material-symbols.scss diff --git a/frontend/src/css/lib/fonts/material-symbols.woff2 b/frontend/src/resources/css/lib/fonts/material-symbols.woff2 similarity index 100% rename from frontend/src/css/lib/fonts/material-symbols.woff2 rename to frontend/src/resources/css/lib/fonts/material-symbols.woff2 diff --git a/frontend/src/css/lib/fonts/montserrat.scss b/frontend/src/resources/css/lib/fonts/montserrat.scss similarity index 100% rename from frontend/src/css/lib/fonts/montserrat.scss rename to frontend/src/resources/css/lib/fonts/montserrat.scss diff --git a/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 b/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 rename to frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2 diff --git a/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 b/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 rename to frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2 diff --git a/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 b/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 rename to frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2 diff --git a/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 b/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 similarity index 100% rename from frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 rename to frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2 diff --git a/frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 b/frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 similarity index 100% rename from frontend/src/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 rename to frontend/src/resources/css/lib/fonts/montserrat/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2 diff --git a/frontend/src/css/style.scss b/frontend/src/resources/css/style.scss similarity index 100% rename from frontend/src/css/style.scss rename to frontend/src/resources/css/style.scss diff --git a/frontend/src/images/default-avatar.png b/frontend/src/resources/images/default-avatar.png similarity index 100% rename from frontend/src/images/default-avatar.png rename to frontend/src/resources/images/default-avatar.png diff --git a/frontend/src/images/logo.png b/frontend/src/resources/images/logo.png similarity index 100% rename from frontend/src/images/logo.png rename to frontend/src/resources/images/logo.png diff --git a/frontend/src/leftpanel.ts b/frontend/src/userPanel/leftpanel.ts similarity index 95% rename from frontend/src/leftpanel.ts rename to frontend/src/userPanel/leftpanel.ts index 62806b1..652c8db 100644 --- a/frontend/src/leftpanel.ts +++ b/frontend/src/userPanel/leftpanel.ts @@ -20,7 +20,6 @@ const dialogClose = document.getElementById("profile-dialog-close")!; /** * Sets up chat collapse functionality - * @function setupChatCollapse * @private */ function setupChatCollapse(): void { @@ -32,7 +31,6 @@ function setupChatCollapse(): void { /** * Sets up chat switching functionality - * @function setupChatSwitching * @private */ function setupChatSwitching(): void { @@ -51,7 +49,6 @@ function setupChatSwitching(): void { /** * Sets up profile dialog functionality - * @function setupProfileDialog * @private */ function setupProfileDialog(): void { diff --git a/frontend/src/profile/api.ts b/frontend/src/userPanel/profile/api.ts similarity index 91% rename from frontend/src/profile/api.ts rename to frontend/src/userPanel/profile/api.ts index d446bca..e3b7cad 100644 --- a/frontend/src/profile/api.ts +++ b/frontend/src/userPanel/profile/api.ts @@ -5,14 +5,13 @@ * @version 1.0.0 */ -import { getAuthHeaders } from '../auth'; +import { getAuthHeaders } from '../../auth/api'; import type { ProfileData, UploadResponse } from './types'; /** * Loads user profile data from the server * @async - * @function loadProfile - * @returns {Promise} User profile data or null if failed + * @returns User profile data or null if failed * @example * const profile = await loadProfile(); * if (profile) { @@ -38,8 +37,6 @@ export async function loadProfile(): Promise { /** * Uploads a profile picture to the server - * @async - * @function uploadProfilePicture * @param {Blob} file - The image file to upload * @returns {Promise} Upload response with URL or null if failed * @example @@ -47,7 +44,7 @@ export async function loadProfile(): Promise { * const file = fileInput.files[0]; * const result = await uploadProfilePicture(file); * if (result) { - * console.log('Uploaded to:', result.profile_picture_url); + * console.log('Uploaded to:', result.profile_picture_url); * } */ export async function uploadProfilePicture(file: Blob): Promise { @@ -73,8 +70,6 @@ export async function uploadProfilePicture(file: Blob): Promise} data - Profile data to update * @returns {Promise} True if update was successful, false otherwise * @example @@ -103,8 +98,6 @@ export async function updateProfile(data: Partial): Promise} True if update was successful, false otherwise * @example diff --git a/frontend/src/profile/editor.ts b/frontend/src/userPanel/profile/editor.ts similarity index 82% rename from frontend/src/profile/editor.ts rename to frontend/src/userPanel/profile/editor.ts index d37aebf..e1d81df 100644 --- a/frontend/src/profile/editor.ts +++ b/frontend/src/userPanel/profile/editor.ts @@ -7,7 +7,7 @@ import { updateProfile } from './api'; import { loadProfile } from './api'; -import { showSuccess, showError } from '../utils/notification'; +import { showSuccess, showError } from '../../utils/notification'; import type { TextField } from 'mdui/components/text-field'; let profileForm = document.getElementById('profile-form')!; @@ -23,9 +23,6 @@ let isInitialized = false; /** * Sets the username field value * @param {string} value - The username value to set - * @function setUsernameValue - * @example - * setUsernameValue('John Doe'); */ export function setUsernameValue(value: string): void { if (nicknameField && nicknameField.value !== undefined) { @@ -36,9 +33,6 @@ export function setUsernameValue(value: string): void { /** * Sets the description field value * @param {string} value - The description value to set - * @function setDescriptionValue - * @example - * setDescriptionValue('Software Developer'); */ export function setDescriptionValue(value: string): void { if (descriptionField && descriptionField.value !== undefined) { @@ -49,10 +43,6 @@ export function setDescriptionValue(value: string): void { /** * Gets the current username field value * @returns {string} The current username value - * @function getUsernameValue - * @example - * const username = getUsernameValue(); - * console.log('Current username:', username); */ export function getUsernameValue(): string { if (nicknameField && nicknameField.value !== undefined) { @@ -64,10 +54,6 @@ export function getUsernameValue(): string { /** * Gets the current description field value * @returns {string} The current description value - * @function getDescriptionValue - * @example - * const description = getDescriptionValue(); - * console.log('Current description:', description); */ export function getDescriptionValue(): string { if (descriptionField && descriptionField.value !== undefined) { @@ -78,10 +64,6 @@ export function getDescriptionValue(): string { /** * Loads profile data from the server and populates the form fields - * @async - * @function loadProfileData - * @example - * await loadProfileData(); */ export async function loadProfileData(): Promise { const userData = await loadProfile(); @@ -97,8 +79,6 @@ export async function loadProfileData(): Promise { /** * Handles profile form submission - * @async - * @function handleFormSubmission * @param {Event} e - Form submission event * @private */ @@ -124,7 +104,6 @@ async function handleFormSubmission(e: Event): Promise { /** * Sets up form submission handler - * @function setupFormHandler * @private */ function setupFormHandler(): void { @@ -142,9 +121,6 @@ function setupFormHandler(): void { /** * Initializes profile editor functionality - * @function initializeProfileEditor - * @example - * initializeProfileEditor(); */ export function initializeProfileEditor(): void { setupFormHandler(); diff --git a/frontend/src/profile/image-cropper.ts b/frontend/src/userPanel/profile/imageCropper.ts similarity index 91% rename from frontend/src/profile/image-cropper.ts rename to frontend/src/userPanel/profile/imageCropper.ts index c4468d1..a76e32a 100644 --- a/frontend/src/profile/image-cropper.ts +++ b/frontend/src/userPanel/profile/imageCropper.ts @@ -5,7 +5,7 @@ * @version 1.0.0 */ -import type { Size2D } from "../types"; +import type { Size2D } from "../../core/types"; /** * Image cropper class for circular profile picture cropping @@ -63,7 +63,6 @@ export class ImageCropper { /** * Sets up mouse and touch event listeners - * @function setupEventListeners * @private */ private setupEventListeners(): void { @@ -78,7 +77,6 @@ export class ImageCropper { /** * Handles mouse down events * @param {MouseEvent} e - Mouse event - * @function onMouseDown * @private */ private onMouseDown(e: MouseEvent): void { @@ -89,7 +87,6 @@ export class ImageCropper { /** * Handles mouse move events during dragging * @param {MouseEvent} e - Mouse event - * @function onMouseMove * @private */ private onMouseMove(e: MouseEvent): void { @@ -107,7 +104,6 @@ export class ImageCropper { /** * Handles mouse up events - * @function onMouseUp * @private */ private onMouseUp(): void { @@ -117,7 +113,6 @@ export class ImageCropper { /** * Handles touch start events * @param {TouchEvent} e - Touch event - * @function onTouchStart * @private */ private onTouchStart(e: TouchEvent): void { @@ -130,7 +125,6 @@ export class ImageCropper { /** * Handles touch move events during dragging * @param {TouchEvent} e - Touch event - * @function onTouchMove * @private */ private onTouchMove(e: TouchEvent): void { @@ -150,7 +144,6 @@ export class ImageCropper { /** * Handles touch end events - * @function onTouchEnd * @private */ private onTouchEnd(): void { @@ -161,9 +154,6 @@ export class ImageCropper { * Loads an image file for cropping * @param {File} file - Image file to load * @returns {Promise} Promise that resolves when image is loaded - * @async - * @example - * await cropper.loadImage(fileInput.files[0]); */ loadImage(file: File): Promise { return new Promise((resolve) => { @@ -178,7 +168,6 @@ export class ImageCropper { /** * Renders the image with circular crop overlay - * @function render * @private */ private render(): void { @@ -216,9 +205,6 @@ export class ImageCropper { /** * Gets the cropped image as a data URL * @returns {string} Data URL of the cropped image - * @example - * const croppedImage = cropper.getCroppedImage(); - * // Use croppedImage as src for an img element */ getCroppedImage(): string { return this.canvas.toDataURL('image/jpeg', 0.8); @@ -226,9 +212,6 @@ export class ImageCropper { /** * Destroys the cropper and removes the canvas from DOM - * @function destroy - * @example - * cropper.destroy(); */ destroy(): void { if (this.canvas.parentNode) { diff --git a/frontend/src/profile.ts b/frontend/src/userPanel/profile/profile.ts similarity index 76% rename from frontend/src/profile.ts rename to frontend/src/userPanel/profile/profile.ts index 2ff08f4..7910aa5 100644 --- a/frontend/src/profile.ts +++ b/frontend/src/userPanel/profile/profile.ts @@ -6,9 +6,9 @@ */ import type { Dialog } from "mdui/components/dialog"; -import { loadProfileData } from './profile/editor'; -import { loadProfilePicture, initializeProfileUpload } from "./profile/upload"; -import { initializeProfileEditor } from './profile/editor'; +import { loadProfileData } from './editor'; +import { loadProfilePicture, initializeProfileUpload } from "./upload"; +import { initializeProfileEditor } from './editor'; // Handle profile form submission const form = document.getElementById("profile-form")!; @@ -24,10 +24,6 @@ form.addEventListener("submit", async (e) => { /** * Initializes profile functionality after user login - * @function initializeProfile - * @example - * // Called after successful authentication - * initializeProfile(); */ export function initializeProfile(): void { // Initialize profile modules diff --git a/frontend/src/profile/types.ts b/frontend/src/userPanel/profile/types.ts similarity index 100% rename from frontend/src/profile/types.ts rename to frontend/src/userPanel/profile/types.ts diff --git a/frontend/src/profile/upload.ts b/frontend/src/userPanel/profile/upload.ts similarity index 88% rename from frontend/src/profile/upload.ts rename to frontend/src/userPanel/profile/upload.ts index 0745eb7..043d191 100644 --- a/frontend/src/profile/upload.ts +++ b/frontend/src/userPanel/profile/upload.ts @@ -6,10 +6,10 @@ */ import type { Dialog } from "mdui/components/dialog"; -import { ImageCropper } from './image-cropper'; +import { ImageCropper } from './imageCropper'; import { uploadProfilePicture } from './api'; import { loadProfile } from './api'; -import { showSuccess, showError } from '../utils/notification'; +import { showSuccess, showError } from '../../utils/notification'; /** * Global image cropper instance @@ -33,8 +33,6 @@ let cropperArea = document.getElementById('cropper-area')!; /** * Opens the image cropper with the selected file - * @async - * @function openCropper * @param {File} file - The image file to crop * @private */ @@ -54,7 +52,6 @@ async function openCropper(file: File): Promise { /** * Closes the image cropper and cleans up resources - * @function closeCropper * @private */ function closeCropper(): void { @@ -69,8 +66,6 @@ function closeCropper(): void { /** * Saves the cropped image and uploads it to the server - * @async - * @function saveCroppedImage * @private */ async function saveCroppedImage(): Promise { @@ -87,7 +82,7 @@ async function saveCroppedImage(): Promise { if (result) { // Update profile picture display const profilePicture = document.getElementById('profile-picture') as HTMLImageElement; - profilePicture.src = result.profile_picture_url + '?t=' + Date.now(); // Cache bust + profilePicture.src = `${result.profile_picture_url}?t=${Date.now()}`; // Cache bust // Close cropper closeCropper(); @@ -101,7 +96,6 @@ async function saveCroppedImage(): Promise { /** * Sets up event listeners for upload functionality - * @function setupEventListeners * @private */ function setupEventListeners(): void { @@ -136,9 +130,6 @@ function setupEventListeners(): void { /** * Loads and displays the user's profile picture * @async - * @function loadProfilePicture - * @example - * await loadProfilePicture(); */ export async function loadProfilePicture(): Promise { const userData = await loadProfile(); @@ -154,9 +145,6 @@ export async function loadProfilePicture(): Promise { /** * Initializes profile upload functionality - * @function initializeProfileUpload - * @example - * initializeProfileUpload(); */ export function initializeProfileUpload(): void { setupEventListeners(); diff --git a/frontend/src/settings.ts b/frontend/src/userPanel/settings.ts similarity index 95% rename from frontend/src/settings.ts rename to frontend/src/userPanel/settings.ts index 3c66910..0b7d8f4 100644 --- a/frontend/src/settings.ts +++ b/frontend/src/userPanel/settings.ts @@ -32,7 +32,6 @@ const panelMapping = { /** * Handles click events on settings list items * @param {Element} item - The clicked list item element - * @function handleListItemClick * @private */ function handleListItemClick(item: Element): void { @@ -58,7 +57,6 @@ function handleListItemClick(item: Element): void { /** * Sets up click listeners for all settings list items - * @function setupSettingsNavigation * @private */ function setupSettingsNavigation(): void { @@ -70,7 +68,6 @@ function setupSettingsNavigation(): void { /** * Resets settings dialog to show the first panel - * @function resetToFirstPanel * @private */ function resetToFirstPanel(): void { @@ -86,7 +83,6 @@ function resetToFirstPanel(): void { /** * Sets up dialog event listeners - * @function setupDialogListeners * @private */ function setupDialogListeners(): void { diff --git a/frontend/src/utils/notification.ts b/frontend/src/utils/notification.ts index b655dc1..ebb6825 100644 --- a/frontend/src/utils/notification.ts +++ b/frontend/src/utils/notification.ts @@ -15,7 +15,6 @@ export type NotificationType = 'success' | 'error'; * Shows a notification with the specified message and type * @param {string} message - The message to display * @param {NotificationType} type - The type of notification (success or error) - * @function showNotification * @private */ function showNotification(message: string, type: NotificationType): void { @@ -49,9 +48,6 @@ function showNotification(message: string, type: NotificationType): void { /** * Shows a success notification * @param {string} message - The success message to display - * @function showSuccess - * @example - * showSuccess('Profile updated successfully!'); */ export function showSuccess(message: string): void { showNotification(message, 'success'); @@ -60,9 +56,6 @@ export function showSuccess(message: string): void { /** * Shows an error notification * @param {string} message - The error message to display - * @function showError - * @example - * showError('Failed to update profile'); */ export function showError(message: string): void { showNotification(message, 'error'); diff --git a/frontend/src/websocket.ts b/frontend/src/websocket.ts index d3967c4..28b0199 100644 --- a/frontend/src/websocket.ts +++ b/frontend/src/websocket.ts @@ -5,14 +5,12 @@ * @version 1.0.0 */ -import { handleWebSocketMessage } from "./chat"; -import { API_FULL_BASE_URL } from "./config"; -import type { WebSocketMessage } from "./types"; +import { handleWebSocketMessage } from "./chat/chat"; +import { API_WS_BASE_URL } from "./core/config"; import { delay } from "./utils/utils"; /** * Creates a new WebSocket connection to the chat server - * @function create * @returns {WebSocket} New WebSocket instance * @private */ @@ -22,7 +20,7 @@ function create(): WebSocket { prefix = "wss://"; } - return new WebSocket(`${prefix}${API_FULL_BASE_URL}/chat/ws`); + return new WebSocket(`${prefix}${API_WS_BASE_URL}/chat/ws`); } /** @@ -31,16 +29,14 @@ function create(): WebSocket { */ export let websocket: WebSocket = create(); -// -------------- -// Initialization -// -------------- - -websocket.addEventListener("message", (e) => { - const message: WebSocketMessage = JSON.parse(e.data); - handleWebSocketMessage(message); -}); - -websocket.addEventListener("error", async () => { +/** + * This function will wait 3 seconds and them attempts to reconnect the WebSocket. + * If it fails, tries again in an endless loop until the connection is established + * again. + * + * @private + */ +async function onError() { console.warn("WebSocket disconnected, retrying in 3 seconds..."); await delay(3000); websocket = create(); @@ -52,4 +48,14 @@ websocket.addEventListener("error", async () => { } websocket.addEventListener("open", listener); -}); \ No newline at end of file + websocket.addEventListener("error", onError); +} + +// -------------- +// Initialization +// -------------- + +websocket.addEventListener("message", (e) => { + handleWebSocketMessage(JSON.parse(e.data)); +}); +websocket.addEventListener("error", onError); \ No newline at end of file