2 directories, 29 files

tinai

Home / testing / ai / tinai
import Api from './api.js';
import Storage from './storage.js';
import { getEl, addSafeEventListener, setElementDisplay } from './util/dom-utils.js';
import { applyTheme, fadeOutLoader } from './util/theme-utils.js';
import { customConfirm } from './util/confirm-dialog.js';

/**
 * Users authentication, profile management, and administrative control.
 */
class Users {

	#api;
	#storage;

	loginForm;
	usernameInput;
	passcodeInput;
	loginButton;
	errorMessageDiv;
	logoutButton;

	// Profile Form Elements
	profileForm;
	latestTimezoneInput;
	updateTimezoneButton;
	profileStatusMessageDiv;

	// Registration Form Elements
	registrationForm;
	regUsernameInput;
	regPasscodeInput;
	regAdminCheckbox;
	regStatusMessageDiv;

	// User Management Elements
	userListContainer;
	loadUsersButton;
	userManagementStatusDiv;

	// Edit User Modal Elements
	editUserModal;
	editUsernameInput;
	editPasscodeInput;
	adminCheckbox;
	saveUserButton;
	cancelEditButton;
	editUserStatusDiv;

	editingUserId = null;

	/**
	 * Initializes the Users class, UI element bindings, and session check.
	 */
	constructor() {
		this.#api = new Api();
		this.#storage = new Storage();

		// Apply theme on initialization
		this.applySavedTheme();

		this.loginForm = getEl('login-form') || getEl('form-login');
		this.usernameInput = getEl('username') || getEl('login-username');
		this.passcodeInput = getEl('passcode') || getEl('login-passcode');
		this.loginButton = getEl('login-button') || getEl('btn-login');
		this.errorMessageDiv = getEl('login-error-message');
		this.logoutButton = getEl('id-btn-logout') || getEl('btn-logout');

		this.profileForm = getEl('id-form-profile-options');
		this.latestTimezoneInput = getEl('id-input-latest-timezone') || getEl('profile-latest-timezone');
		this.updateTimezoneButton = getEl('id-btn-update-timezone') || getEl('btn-update-timezone');
		this.profileStatusMessageDiv = getEl('profile-status-message');

		this.registrationForm = getEl('registration-form');
		this.regUsernameInput = getEl('reg-username');
		this.regPasscodeInput = getEl('reg-passcode');
		this.regAdminCheckbox = getEl('reg-admin');
		this.regStatusMessageDiv = getEl('reg-status-message');

		this.userListContainer = getEl('id-div-user-table-container') || getEl('user-list-container');
		this.loadUsersButton = getEl('btn-load-users');
		this.userManagementStatusDiv = getEl('user-management-status');

		this.editUserModal = getEl('edit-user-modal');
		this.editUsernameInput = getEl('id-username') || getEl('edit-username');
		this.editPasscodeInput = getEl('id-passcode') || getEl('edit-passcode');
		this.adminCheckbox = getEl('id-admin') || getEl('edit-admin');
		this.saveUserButton = getEl('id-btn-save-user') || getEl('btn-save-user');
		this.cancelEditButton = getEl('id-btn-clear-user-form') || getEl('btn-cancel-edit');
		this.editUserStatusDiv = getEl('edit-user-status');

		this.initEventListeners();
		if (!this.loginForm) {
			this.checkLoginStatus();
		} else {
			fadeOutLoader();
		}
	}

	/**
	 * Applies the saved theme from configuration.
	 */
	applySavedTheme() {
		const config = this.#storage.get_app_config();
		const theme = (config && config[this.#storage.KEY_CONFIG_THEME]) ? config[this.#storage.KEY_CONFIG_THEME] : 'theme_dark';
		applyTheme(theme, true);
	}

	/**
	 * Binds event listeners for user login, logout, profile, and admin forms.
	 */
	initEventListeners() {
		if (this.loginForm) {
			addSafeEventListener(this.loginForm, 'submit', (e) => {
				e.preventDefault();
				this.handleLogin();
			});
		}
		addSafeEventListener(this.loginButton, 'click', (e) => {
			if (e) e.preventDefault();
			this.handleLogin();
		});
		addSafeEventListener(this.logoutButton, 'click', (e) => {
			if (e) e.preventDefault();
			this.handleLogout();
		});
		addSafeEventListener(this.updateTimezoneButton, 'click', (e) => {
			if (e) e.preventDefault();
			this.handleUpdateTimezone();
		});

		const registerButton = getEl('btn-register');
		addSafeEventListener(registerButton, 'click', this.handleRegister.bind(this));

		addSafeEventListener(this.loadUsersButton, 'click', this.handleLoadUsers.bind(this));
		addSafeEventListener(this.saveUserButton, 'click', this.handleSaveUser.bind(this));
		addSafeEventListener(this.cancelEditButton, 'click', this.hideEditUserModal.bind(this));

		const showRegisterButton = getEl('btn-show-register');
		if (showRegisterButton && this.registrationForm) {
			addSafeEventListener(showRegisterButton, 'click', () => {
				const isHidden = this.registrationForm.style.display === 'none' || this.registrationForm.style.display === '';
				setElementDisplay(this.registrationForm, isHidden ? 'block' : 'none');
				showRegisterButton.textContent = isHidden ? 'Cancel Registration' : 'Register New User';
			});
		}

		const timezoneSelect = getEl('profile-timezone-select');
		if (timezoneSelect && this.latestTimezoneInput) {
			addSafeEventListener(timezoneSelect, 'change', (e) => {
				this.latestTimezoneInput.value = e.target.value;
			});
		}
	}

	/**
	 * Handles authentication request on login form submission.
	 */
	handleLogin() {
		if (!this.usernameInput || !this.passcodeInput) return;
		const username = this.usernameInput.value;
		const passcode = this.passcodeInput.value;

		if (!username || !passcode) {
			this.displayErrorMessage('Please enter both username and passcode.');
			return;
		}

		this.displayErrorMessage('');

		this.#api.post(
			{ action: 'login', username, passcode },
			(response) => {
				if (response.success) {
					window.location.reload();
				} else {
					this.displayErrorMessage(response.error || response.message || 'Login failed.');
				}
			},
			(error) => {
				this.displayErrorMessage('An error occurred during login. Please try again.');
				console.error('Login error:', error);
			},
			'users'
		);
	}

	/**
	 * Handles user logout and reloads the application.
	 */
	handleLogout() {
		this.#api.post(
			{ action: 'logout' },
			(response) => {
				if (response.success) {
					window.location.reload();
				} else {
					alert(response.message || response.error || 'Logout failed.');
				}
			},
			(error) => {
				alert('An error occurred during logout. Please try again.');
				console.error('Logout error:', error);
			},
			'users'
		);
	}

	/**
	 * Displays an authentication error message.
	 * @param {string} message - Error message text.
	 */
	displayErrorMessage(message) {
		if (this.errorMessageDiv) {
			this.errorMessageDiv.textContent = message;
			setElementDisplay(this.errorMessageDiv, message ? 'block' : 'none');
		}
	}

	/**
	 * Checks current session authentication status with the server.
	 */
	checkLoginStatus() {
		this.#api.post(
			{ action: 'session' },
			(response) => {
				if (response['isLoggedIn']) {
					setElementDisplay(this.loginForm, 'none');
					setElementDisplay(this.logoutButton, 'block');
					this.loadProfile();
					if (response['isAdmin']) {
						this.setupAdminInterface();
					}
				} else {
					setElementDisplay(this.loginForm, 'block');
					setElementDisplay(this.logoutButton, 'none');
					setElementDisplay(this.profileForm, 'none');
					fadeOutLoader();
				}
			},
			(error) => {
				console.error('Check login status error:', error);
				setElementDisplay(this.loginForm, 'block');
				setElementDisplay(this.logoutButton, 'none');
				setElementDisplay(this.profileForm, 'none');
				fadeOutLoader();
			},
			'users'
		);
	}

	/**
	 * Fetches user profile data from the server.
	 */
	loadProfile() {
		this.#api.post(
			{ action: 'get_profile' },
			(response) => {
				if (response.success && response['profile']) {
					this.displayProfile(response['profile']);
				} else if (response.success && !response['profile']) {
					this.displayProfileStatus('Profile not found, but you can create one.', false);
					setElementDisplay(this.profileForm, 'block');
				} else {
					this.displayProfileStatus(response.message || response.error || 'Failed to load profile.', true);
				}
				fadeOutLoader();
			},
			(error) => {
				this.displayProfileStatus('An error occurred while loading profile.', true);
				console.error('Load profile error:', error);
				fadeOutLoader();
			},
			'users'
		);
	}

	/**
	 * Populates the profile form controls with loaded user data.
	 * @param {object} profile - User profile data object.
	 */
	displayProfile(profile) {
		if (this.latestTimezoneInput && profile.latest_timezone) {
			this.latestTimezoneInput.value = profile.latest_timezone;
		}
		const timezoneSelect = getEl('profile-timezone-select');
		if (timezoneSelect && profile.latest_timezone) {
			timezoneSelect.value = profile.latest_timezone;
		}
		setElementDisplay(this.profileForm, 'block');
	}

	/**
	 * Submits the updated timezone preference to the server.
	 */
	handleUpdateTimezone() {
		if (!this.latestTimezoneInput) return;
		const latest_timezone = this.latestTimezoneInput.value;

		this.#api.post(
			{ action: 'update_timezone', latest_timezone },
			(response) => {
				if (response.success) {
					this.displayProfileStatus('Timezone updated successfully!', false);
				} else {
					this.displayProfileStatus(response.message || response.error || 'Failed to update timezone.', true);
				}
			},
			(error) => {
				this.displayProfileStatus('An error occurred while updating timezone.', true);
				console.error('Update timezone error:', error);
			},
			'users'
		);
	}

	/**
	 * Displays a temporary status message in the profile form.
	 * @param {string} message - Status message text.
	 * @param {boolean} isError - Whether the status represents an error.
	 */
	displayProfileStatus(message, isError) {
		if (this.profileStatusMessageDiv) {
			this.profileStatusMessageDiv.textContent = message;
			this.profileStatusMessageDiv.className = isError ? 'status-error' : 'status-success';
			setElementDisplay(this.profileStatusMessageDiv, 'block');
			setTimeout(() => {
				setElementDisplay(this.profileStatusMessageDiv, 'none');
			}, 5000);
		}
	}

	/**
	 * Displays admin UI elements and loads user list if authorized.
	 */
	setupAdminInterface() {
		const adminNavButton = getEl('id-btn-admin');
		if (adminNavButton) {
			setElementDisplay(adminNavButton, 'block');
		}
		const adminForm = getEl('id-form-admin-options');
		if (adminForm) {
			setElementDisplay(adminForm, 'block');
		}
		this.handleLoadUsers();
	}

	/**
	 * Handles user registration form submission by an admin.
	 */
	handleRegister() {
		if (!this.regUsernameInput || !this.regPasscodeInput) return;
		const username = this.regUsernameInput.value;
		const passcode = this.regPasscodeInput.value;
		const admin = this.regAdminCheckbox ? this.regAdminCheckbox.checked : false;

		if (!username || !passcode) {
			this.displayRegStatus('Please enter both username and passcode.', true);
			return;
		}

		this.#api.post(
			{ action: 'add_or_update_user', username, passcode, admin },
			(response) => {
				if (response.success) {
					this.displayRegStatus('User registered successfully!', false);
					this.regUsernameInput.value = '';
					this.regPasscodeInput.value = '';
					if (this.regAdminCheckbox) this.regAdminCheckbox.checked = false;
					this.handleLoadUsers();
				} else {
					this.displayRegStatus(response.message || response.error || 'Registration failed.', true);
				}
			},
			(error) => {
				this.displayRegStatus('An error occurred during registration.', true);
				console.error('Registration error:', error);
			},
			'users'
		);
	}

	/**
	 * Displays a temporary status message in the registration form.
	 * @param {string} message - Status message text.
	 * @param {boolean} isError - Whether the status represents an error.
	 */
	displayRegStatus(message, isError) {
		if (this.regStatusMessageDiv) {
			this.regStatusMessageDiv.textContent = message;
			this.regStatusMessageDiv.className = isError ? 'status-error' : 'status-success';
			setElementDisplay(this.regStatusMessageDiv, 'block');
			setTimeout(() => {
				setElementDisplay(this.regStatusMessageDiv, 'none');
			}, 5000);
		}
	}

	/**
	 * Requests the list of all registered users for admin management.
	 */
	handleLoadUsers() {
		this.#api.post(
			{ action: 'get_all_users' },
			(response) => {
				if (response.success && response['users']) {
					this.renderUserTable(response['users']);
				} else {
					this.displayUserManagementStatus(response.message || response.error || 'Failed to load users.', true);
				}
			},
			(error) => {
				this.displayUserManagementStatus('An error occurred while loading users.', true);
				console.error('Load users error:', error);
			},
			'users'
		);
	}

	/**
	 * Renders the user list table and attaches row action listeners.
	 * @param {Array<object>} users - Array of user objects.
	 */
	renderUserTable(users) {
		if (!this.userListContainer) return;

		let html = `
			<table class="user-management-table" style="width: 100%; border-collapse: collapse;">
				<thead>
					<tr>
						<th>ID</th>
						<th>Username</th>
						<th>Admin</th>
						<th>Created At</th>
						<th>Actions</th>
					</tr>
				</thead>
				<tbody>
		`;

		users.forEach(user => {
			html += `
				<tr data-user-id="${user.id}">
					<td>${user.id}</td>
					<td>${user.username}</td>
					<td>${Number(user.admin) === 1 ? 'Yes' : 'No'}</td>
					<td>${user.created_at || 'N/A'}</td>
					<td>
						<button class="btn-edit-user" data-user-id="${user.id}">Edit</button>
						<button class="btn-delete-user" data-user-id="${user.id}">Delete</button>
					</td>
				</tr>
			`;
		});

		html += `
				</tbody>
			</table>
		`;

		this.userListContainer.innerHTML = html;
		this.attachUserTableEventListeners();
	}

	/**
	 * Binds click events for edit and delete buttons in the user table.
	 */
	attachUserTableEventListeners() {
		if (!this.userListContainer) return;

		this.userListContainer.querySelectorAll('.btn-edit-user').forEach(button => {
			addSafeEventListener(button, 'click', (e) => {
				const userId = e.target.dataset.userId;
				this.showEditUserModal(userId);
			});
		});

		this.userListContainer.querySelectorAll('.btn-delete-user').forEach(button => {
			addSafeEventListener(button, 'click', async (e) => {
				const userId = e.target.dataset.userId;
				if (await customConfirm('Delete User', `Are you sure you want to delete user ID ${userId}?`)) {
					this.handleDeleteUser(userId);
				}
			});
		});
	}

	/**
	 * Displays the edit user modal populated with the selected user data.
	 * @param {string|number} userId - The ID of the user to edit.
	 */
	showEditUserModal(userId) {
		this.editingUserId = userId;
		this.#api.post(
			{ action: 'get_all_users' },
			(response) => {
				if (response.success && response['users']) {
					const user = response['users'].find(u => String(u.id) === String(userId));
					if (user) {
						if (this.editUsernameInput) this.editUsernameInput.value = user.username;
						if (this.editPasscodeInput) this.editPasscodeInput.value = '';
						if (this.adminCheckbox) this.adminCheckbox.checked = Number(user.admin) === 1;
						if (this.editUserModal) setElementDisplay(this.editUserModal, 'block');
					} else {
						this.displayUserManagementStatus('User not found.', true);
					}
				}
			},
			(error) => {
				this.displayUserManagementStatus('Failed to retrieve user details for editing.', true);
				console.error(error);
			},
			'users'
		);
	}

	/**
	 * Closes and resets the edit user modal form.
	 */
	hideEditUserModal() {
		this.editingUserId = null;
		if (this.editUserModal) setElementDisplay(this.editUserModal, 'none');
		if (this.editUsernameInput) this.editUsernameInput.value = '';
		if (this.editPasscodeInput) this.editPasscodeInput.value = '';
		if (this.adminCheckbox) this.adminCheckbox.checked = false;
	}

	/**
	 * Submits edited user changes to the server.
	 */
	handleSaveUser() {
		if (!this.editingUserId) return;

		const updatedData = {
			action: 'add_or_update_user',
			id: this.editingUserId
		};

		if (this.editUsernameInput && this.editUsernameInput.value) {
			updatedData.username = this.editUsernameInput.value;
		}
		if (this.editPasscodeInput && this.editPasscodeInput.value) {
			updatedData.passcode = this.editPasscodeInput.value;
		}
		if (this.adminCheckbox) {
			updatedData.admin = this.adminCheckbox.checked;
		}

		this.#api.post(
			updatedData,
			(response) => {
				if (response.success) {
					this.displayUserManagementStatus('User updated successfully!', false);
					this.hideEditUserModal();
					this.handleLoadUsers();
				} else {
					this.displayEditUserStatus(response.message || response.error || 'Failed to update user.', true);
				}
			},
			(error) => {
				this.displayEditUserStatus('An error occurred while updating user.', true);
				console.error('Save user error:', error);
			},
			'users'
		);
	}

	/**
	 * Submits a request to delete a specified user.
	 * @param {string|number} userId - The ID of the user to delete.
	 */
	handleDeleteUser(userId) {
		this.#api.post(
			{ action: 'delete_user', id: userId },
			(response) => {
				if (response.success) {
					this.displayUserManagementStatus('User deleted successfully!', false);
					this.handleLoadUsers();
				} else {
					this.displayUserManagementStatus(response.message || response.error || 'Failed to delete user.', true);
				}
			},
			(error) => {
				this.displayUserManagementStatus('An error occurred while deleting user.', true);
				console.error('Delete user error:', error);
			},
			'users'
		);
	}

	/**
	 * Displays a temporary status message in the user management panel.
	 * @param {string} message - Status message text.
	 * @param {boolean} isError - Whether the status represents an error.
	 */
	displayUserManagementStatus(message, isError) {
		if (this.userManagementStatusDiv) {
			this.userManagementStatusDiv.textContent = message;
			this.userManagementStatusDiv.className = isError ? 'status-error' : 'status-success';
			setElementDisplay(this.userManagementStatusDiv, 'block');
			setTimeout(() => {
				setElementDisplay(this.userManagementStatusDiv, 'none');
			}, 5000);
		}
	}

	/**
	 * Displays a temporary status message in the edit user modal.
	 * @param {string} message - Status message text.
	 * @param {boolean} isError - Whether the status represents an error.
	 */
	displayEditUserStatus(message, isError) {
		if (this.editUserStatusDiv) {
			this.editUserStatusDiv.textContent = message;
			this.editUserStatusDiv.className = isError ? 'status-error' : 'status-success';
			setElementDisplay(this.editUserStatusDiv, 'block');
			setTimeout(() => {
				setElementDisplay(this.editUserStatusDiv, 'none');
			}, 5000);
		}
	}
}

export default Users;
🌐
users.js ×
Type: Web, text/plain
18.18 Kilobytes
Last Modified 2026-09-04 14:24:16
⬇ Download File