Files
singlechat/client/src/views/ChatView.vue
Torsten Schulz (local) 1ecf7b6ba7 Add Ratgeber section with new routes and links
- Introduced a new "Ratgeber" link in ImprintContainer.vue for better user navigation.
- Added multiple routes for the Ratgeber section, including GuideHubView, GuideFirstMessageView, GuideProfileView, GuideSafetyView, and GuideRedFlagsView, enhancing content accessibility.
- Implemented SEO metadata for the new routes to improve search engine visibility and user engagement.

These changes collectively enhance the site's informational resources and improve navigation for users seeking guidance on chat-related topics.
2026-04-27 14:24:42 +02:00

361 lines
11 KiB
Vue

<template>
<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">
<div class="sidebar-brand-head">
<span class="app-brand-mark sidebar-brand-mark" aria-hidden="true">S</span>
<strong>SingleChat</strong>
</div>
<span>Online Chat</span>
</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>
</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>
</div>
</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>
<HeaderAdBanner />
</header>
<div class="horizontal-box horizontal-box-login">
<div class="content login-content-shell">
<div class="login-screen">
<LoginForm />
</div>
</div>
</div>
<ImprintContainer />
</template>
</div>
</template>
<script setup>
import { onMounted, computed } from 'vue';
import { useChatStore } from '../stores/chat';
import MenuBar from '../components/MenuBar.vue';
import UserList from '../components/UserList.vue';
import LoginForm from '../components/LoginForm.vue';
import ChatWindow from '../components/ChatWindow.vue';
import ChatInput from '../components/ChatInput.vue';
import SearchView from '../components/SearchView.vue';
import InboxView from '../components/InboxView.vue';
import HistoryView from '../components/HistoryView.vue';
import ImprintContainer from '../components/ImprintContainer.vue';
import HeaderAdBanner from '../components/HeaderAdBanner.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);
});
onMounted(async () => {
// Versuche Session wiederherzustellen
const sessionRestored = await chatStore.restoreSession();
if (!sessionRestored) {
// Keine gültige Session, versuche trotzdem WebSocket-Verbindung herzustellen
// Die Verbindung wird beim Login automatisch wiederhergestellt, falls nötig
try {
await chatStore.connectWebSocket();
} catch (error) {
console.log('WebSocket-Verbindung beim Laden fehlgeschlagen (wird beim Login automatisch wiederhergestellt):', error.message);
}
}
});
</script>
<style scoped>
.main-content-wrapper {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
height: 100%;
}
.login-screen {
flex: 1;
min-height: 0;
padding: 28px;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
background:
radial-gradient(circle at left top, rgba(61, 134, 84, 0.2), transparent 24%),
radial-gradient(circle at right bottom, rgba(36, 92, 58, 0.12), transparent 26%),
linear-gradient(180deg, rgba(231, 241, 234, 0.95) 0%, rgba(237, 242, 238, 0.96) 48%, rgba(227, 236, 229, 0.98) 100%);
}
.horizontal-box-login {
display: block;
}
.chat-content {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
height: 100%;
}
.chat-header {
padding: 0.7rem 1rem;
flex-shrink: 0;
border-bottom: 1px solid var(--color-border);
display: flex;
align-items: center;
gap: 0.85rem;
background: linear-gradient(180deg, rgba(225, 239, 229, 0.92) 0%, rgba(247, 250, 248, 0.9) 100%);
}
.chat-header-accent {
width: 0.6rem;
height: 2.4rem;
border-radius: 999px;
flex-shrink: 0;
}
.chat-header-accent-M {
background: linear-gradient(180deg, #5a94d2 0%, #467bb2 100%);
}
.chat-header-accent-F {
background: linear-gradient(180deg, #ff7eaa 0%, #d85f8c 100%);
}
.chat-header-accent-P {
background: linear-gradient(180deg, #e0ab46 0%, #c78a2c 100%);
}
.chat-header-accent-TF {
background: linear-gradient(180deg, #a37ac8 0%, #8b60af 100%);
}
.chat-header-accent-TM {
background: linear-gradient(180deg, #79b8d0 0%, #5fa2bf 100%);
}
.chat-header-main {
min-width: 0;
}
.chat-header h2 {
margin: 0;
font-size: 1rem;
line-height: 1.2;
color: var(--color-text-strong);
}
.chat-header-info {
margin-top: 0.18rem;
font-size: 0.75rem;
color: var(--color-text-muted);
display: flex;
flex-direction: row;
gap: 0.8rem;
align-items: center;
}
.error-message {
padding: 0.9rem 1rem;
background-color: #fff1f1;
color: #a83f3f;
border: 1px solid #efc3c3;
margin: 0.9rem;
border-radius: 10px;
text-align: center;
font-weight: bold;
}
.command-table-container {
margin: 0.9rem;
border: 1px solid var(--color-border);
border-radius: 12px;
background: var(--color-surface);
overflow: hidden;
}
.command-table-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 0.85rem;
background: var(--color-surface-subtle);
border-bottom: 1px solid var(--color-border);
}
.command-table-close {
border: 1px solid var(--color-border);
background: var(--color-surface);
padding: 0.35rem 0.7rem;
cursor: pointer;
border-radius: 8px;
}
.command-table-scroll {
max-height: 220px;
overflow: auto;
}
.command-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9em;
}
.command-table th,
.command-table td {
padding: 0.5rem 0.65rem;
border-bottom: 1px solid #edf1ee;
text-align: left;
}
.command-table th {
background: #f9fbfa;
position: sticky;
top: 0;
}
</style>