Refactor chat interface and enhance user experience

- Updated the ChatWindow component to provide clearer instructions and actions when no conversation is selected, improving user guidance.
- Redesigned the MenuBar to display session timeout information more effectively.
- Enhanced the SearchView component with a more user-friendly country selection using a Multiselect dropdown.
- Improved the UserList component to display user age and gender, enhancing user profile visibility.
- Updated various views (ChatView, FaqView, FeedbackView, PartnersView, RulesView, SafetyView) to include a consistent app branding link for better navigation.

These changes collectively enhance the chat interface, improve user engagement, and streamline navigation across the application.
This commit is contained in:
Torsten Schulz (local)
2026-04-20 12:01:26 +02:00
parent 0fcc6878bd
commit 336e8308cf
12 changed files with 1024 additions and 224 deletions

View File

@@ -1,79 +1,141 @@
<template>
<div class="chat-container">
<header class="header">
<div class="app-brand">
<span class="app-brand-mark">S</span>
<div class="app-brand-copy">
<span class="app-brand-eyebrow">SingleChat</span>
<h1>Chat</h1>
</div>
</div>
<div v-if="chatStore.isLoggedIn" class="header-status">
<span class="header-status-chip">{{ chatStore.userName }}</span>
<span v-if="chatStore.isoCountryCode" class="header-status-chip">{{ chatStore.isoCountryCode }}</span>
</div>
</header>
<MenuBar v-if="chatStore.isLoggedIn" />
<div class="horizontal-box" :class="{ 'horizontal-box-login': !chatStore.isLoggedIn, 'horizontal-box-app': chatStore.isLoggedIn }">
<UserList v-if="chatStore.isLoggedIn" />
<div class="content">
<div v-if="!chatStore.isLoggedIn" class="login-screen">
<LoginForm />
</div>
<div v-else class="main-content-wrapper">
<div v-if="chatStore.errorMessage" class="error-message">
{{ chatStore.errorMessage }}
<div class="chat-container" :class="{ 'chat-container-auth': chatStore.isLoggedIn }">
<template v-if="chatStore.isLoggedIn">
<div class="auth-main-layout">
<aside class="app-sidebar">
<div class="sidebar-brand">
<strong>SingleChat</strong>
<span>Online Chat</span>
</div>
<div v-if="chatStore.commandTable" class="command-table-container">
<div class="command-table-header">
<strong>{{ chatStore.commandTable.title }}</strong>
<button class="command-table-close" @click="chatStore.clearCommandTable()">Schließen</button>
</div>
<div class="command-table-scroll">
<table class="command-table">
<thead>
<tr>
<th v-for="(column, idx) in chatStore.commandTable.columns" :key="`head-${idx}`">
{{ column }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIdx) in chatStore.commandTable.rows" :key="`row-${rowIdx}`">
<td v-for="(cell, cellIdx) in row" :key="`cell-${rowIdx}-${cellIdx}`">
{{ cell }}
</td>
</tr>
</tbody>
</table>
</div>
<nav class="sidebar-nav" aria-label="Hauptnavigation">
<button
type="button"
:class="{ 'is-active': chatStore.currentView === 'chat' }"
@click="goLobby"
>
<span class="nav-icon" aria-hidden="true">🏠</span>
Lobby
</button>
<button
type="button"
:class="{ 'is-active': chatStore.currentView === 'inbox', 'has-unread': chatStore.unreadChatsCount > 0 }"
@click="chatStore.setView('inbox')"
>
<span class="nav-icon" aria-hidden="true">📥</span>
{{ $t('menu_inbox') }}
<span v-if="chatStore.unreadChatsCount > 0" class="sidebar-badge">{{ chatStore.unreadChatsCount }}</span>
</button>
<button
type="button"
:class="{ 'is-active': chatStore.currentView === 'history' }"
@click="chatStore.setView('history')"
>
<span class="nav-icon" aria-hidden="true">🕘</span>
{{ $t('menu_history') }}
</button>
<button
type="button"
:class="{ 'is-active': chatStore.currentView === 'search' }"
@click="chatStore.setView('search')"
>
<span class="nav-icon" aria-hidden="true">🔎</span>
{{ $t('menu_search') }}
</button>
</nav>
<div class="sidebar-profile">
<span class="profile-avatar">{{ userInitials }}</span>
<span>
<strong>{{ chatStore.userName }}</strong>
<small>{{ chatStore.country || 'SingleChat Member' }}</small>
</span>
</div>
<SearchView v-if="chatStore.currentView === 'search'" />
<InboxView v-else-if="chatStore.currentView === 'inbox'" />
<HistoryView v-else-if="chatStore.currentView === 'history'" />
<div v-else class="chat-content">
<div v-if="chatStore.currentConversation && currentUserInfo" class="chat-header">
<span :class="['chat-header-accent', 'chat-header-accent-' + currentUserInfo.gender]"></span>
<div class="chat-header-main">
<h2>{{ chatStore.currentConversation }}</h2>
<div class="chat-header-info">
<span v-if="currentUserInfo">{{ currentUserInfo.country }}</span>
<span v-if="currentUserInfo">{{ currentUserInfo.age }} · {{ currentUserInfo.gender }}</span>
</aside>
<section class="app-workspace">
<header class="workspace-header">
<h1>{{ pageTitle }}</h1>
<MenuBar />
<button type="button" class="icon-button" :title="chatStore.userName">{{ userInitials }}</button>
</header>
<div class="horizontal-box horizontal-box-app">
<UserList />
<div class="content">
<div class="main-content-wrapper">
<div v-if="chatStore.errorMessage" class="error-message">
{{ chatStore.errorMessage }}
</div>
<div v-if="chatStore.commandTable" class="command-table-container">
<div class="command-table-header">
<strong>{{ chatStore.commandTable.title }}</strong>
<button class="command-table-close" @click="chatStore.clearCommandTable()">Schließen</button>
</div>
<div class="command-table-scroll">
<table class="command-table">
<thead>
<tr>
<th v-for="(column, idx) in chatStore.commandTable.columns" :key="`head-${idx}`">
{{ column }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIdx) in chatStore.commandTable.rows" :key="`row-${rowIdx}`">
<td v-for="(cell, cellIdx) in row" :key="`cell-${rowIdx}-${cellIdx}`">
{{ cell }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<SearchView v-if="chatStore.currentView === 'search'" />
<InboxView v-else-if="chatStore.currentView === 'inbox'" />
<HistoryView v-else-if="chatStore.currentView === 'history'" />
<div v-else class="chat-content">
<div v-if="chatStore.currentConversation && currentUserInfo" class="chat-header">
<span :class="['chat-header-accent', 'chat-header-accent-' + currentUserInfo.gender]"></span>
<div class="chat-header-main">
<h2>{{ chatStore.currentConversation }}</h2>
<div class="chat-header-info">
<span v-if="currentUserInfo">{{ currentUserInfo.country }}</span>
<span v-if="currentUserInfo">{{ currentUserInfo.age }} · {{ currentUserInfo.gender }}</span>
</div>
</div>
</div>
<ChatWindow />
</div>
<ChatInput />
</div>
</div>
<ChatWindow />
</div>
<ChatInput />
</section>
</div>
<ImprintContainer />
</template>
<template v-else>
<header class="header">
<div class="app-brand">
<span class="app-brand-mark">S</span>
<div class="app-brand-copy">
<span class="app-brand-eyebrow">SingleChat</span>
<h1>Chat</h1>
</div>
</div>
</header>
<div class="horizontal-box horizontal-box-login">
<div class="content login-content-shell">
<div class="login-screen">
<LoginForm />
</div>
</div>
</div>
</div>
<ImprintContainer />
<ImprintContainer />
</template>
</div>
</template>
@@ -92,6 +154,29 @@ import ImprintContainer from '../components/ImprintContainer.vue';
const chatStore = useChatStore();
const userInitials = computed(() => {
return (chatStore.userName || 'SC')
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map(part => part.charAt(0).toUpperCase())
.join('') || 'SC';
});
const pageTitle = computed(() => {
if (chatStore.currentView === 'search') return 'Suchen';
if (chatStore.currentView === 'inbox') return 'Posteingang';
if (chatStore.currentView === 'history') return 'Verlauf';
if (chatStore.currentConversation) return chatStore.currentConversation;
return 'Lobby';
});
function goLobby() {
chatStore.currentConversation = null;
chatStore.messages = [];
chatStore.setView('chat');
}
const currentUserInfo = computed(() => {
if (!chatStore.currentConversation) return null;
return chatStore.users.find(u => u.userName === chatStore.currentConversation);