Refactor backend CORS settings to include default origins and improve error handling in chat services: Introduce dynamic CORS origin handling, enhance RabbitMQ message sending with fallback mechanisms, and update WebSocket service to manage pending messages. Update UI components for better accessibility and responsiveness, including adjustments to dialog and navigation elements. Enhance styling for improved user experience across various components.

This commit is contained in:
Torsten Schulz (local)
2026-03-19 14:44:04 +01:00
parent 4442937ebd
commit 9d44a265ca
67 changed files with 5426 additions and 1099 deletions

View File

@@ -1,53 +1,59 @@
<template>
<h2>{{ $t('socialnetwork.vocab.chapterTitle', { title: chapter?.title || '' }) }}</h2>
<div class="vocab-chapter-view">
<section class="vocab-chapter-hero surface-card">
<span class="vocab-chapter-hero__eyebrow">Vokabeltrainer</span>
<h2>{{ $t('socialnetwork.vocab.chapterTitle', { title: chapter?.title || '' }) }}</h2>
<p>Kapitelinhalt durchsuchen, Vokabeln pflegen und direkt in die Uebung wechseln.</p>
</section>
<div class="box">
<div v-if="loading">{{ $t('general.loading') }}</div>
<div v-else-if="!chapter">{{ $t('socialnetwork.vocab.notFound') }}</div>
<div v-else>
<div v-show="!practiceOpen">
<div class="row">
<button @click="back">{{ $t('general.back') }}</button>
<button v-if="vocabs.length" @click="openPractice">{{ $t('socialnetwork.vocab.practice.open') }}</button>
<button @click="openSearch">{{ $t('socialnetwork.vocab.search.open') }}</button>
</div>
<div class="row" v-if="chapter.isOwner">
<h3>{{ $t('socialnetwork.vocab.addVocab') }}</h3>
<div class="grid">
<label>
{{ $t('socialnetwork.vocab.learningWord') }}
<input v-model="learning" type="text" />
</label>
<label>
{{ $t('socialnetwork.vocab.referenceWord') }}
<input v-model="reference" type="text" />
</label>
<section class="box surface-card">
<div v-if="loading">{{ $t('general.loading') }}</div>
<div v-else-if="!chapter">{{ $t('socialnetwork.vocab.notFound') }}</div>
<div v-else>
<div v-show="!practiceOpen">
<div class="row row--actions">
<button @click="back">{{ $t('general.back') }}</button>
<button v-if="vocabs.length" @click="openPractice">{{ $t('socialnetwork.vocab.practice.open') }}</button>
<button @click="openSearch">{{ $t('socialnetwork.vocab.search.open') }}</button>
</div>
<div class="editor-card" v-if="chapter.isOwner">
<h3>{{ $t('socialnetwork.vocab.addVocab') }}</h3>
<div class="grid">
<label>
<span>{{ $t('socialnetwork.vocab.learningWord') }}</span>
<input v-model="learning" type="text" />
</label>
<label>
<span>{{ $t('socialnetwork.vocab.referenceWord') }}</span>
<input v-model="reference" type="text" />
</label>
</div>
<button :disabled="saving || !canSave" @click="add">
{{ saving ? $t('socialnetwork.vocab.saving') : $t('socialnetwork.vocab.add') }}
</button>
</div>
<div v-if="vocabs.length === 0" class="empty-state">{{ $t('socialnetwork.vocab.noVocabs') }}</div>
<div v-else class="table-wrap">
<table class="tbl">
<thead>
<tr>
<th>{{ $t('socialnetwork.vocab.learningWord') }}</th>
<th>{{ $t('socialnetwork.vocab.referenceWord') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="v in vocabs" :key="v.id">
<td>{{ v.learning }}</td>
<td>{{ v.reference }}</td>
</tr>
</tbody>
</table>
</div>
<button :disabled="saving || !canSave" @click="add">
{{ saving ? $t('socialnetwork.vocab.saving') : $t('socialnetwork.vocab.add') }}
</button>
</div>
<hr />
<div v-if="vocabs.length === 0">{{ $t('socialnetwork.vocab.noVocabs') }}</div>
<table v-else class="tbl">
<thead>
<tr>
<th>{{ $t('socialnetwork.vocab.learningWord') }}</th>
<th>{{ $t('socialnetwork.vocab.referenceWord') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="v in vocabs" :key="v.id">
<td>{{ v.learning }}</td>
<td>{{ v.reference }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</div>
<VocabPracticeDialog ref="practiceDialog" />
@@ -147,30 +153,120 @@ export default {
</script>
<style scoped>
.box {
background: #f6f6f6;
padding: 12px;
border: 1px solid #ccc;
display: inline-block;
.vocab-chapter-view {
display: grid;
gap: 18px;
}
.vocab-chapter-hero,
.box {
background: linear-gradient(180deg, rgba(255, 252, 247, 0.97), rgba(250, 244, 235, 0.95));
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-soft);
}
.vocab-chapter-hero,
.box {
padding: 22px 24px;
}
.vocab-chapter-hero__eyebrow {
display: inline-flex;
margin-bottom: 8px;
padding: 4px 10px;
border-radius: var(--radius-pill);
background: var(--color-secondary-soft);
color: var(--color-text-secondary);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.vocab-chapter-hero p {
margin: 0;
color: var(--color-text-secondary);
}
.row {
margin-bottom: 10px;
}
.row--actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.editor-card {
display: grid;
gap: 14px;
margin: 18px 0 20px;
padding: 18px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.62);
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 8px;
}
.grid label {
display: grid;
gap: 8px;
}
.grid span {
font-weight: 600;
color: var(--color-text-secondary);
}
.empty-state {
padding: 18px;
border: 1px dashed var(--color-border-strong);
border-radius: var(--radius-md);
color: var(--color-text-secondary);
background: rgba(255, 255, 255, 0.5);
}
.table-wrap {
overflow-x: auto;
}
.tbl {
width: 100%;
border-collapse: collapse;
background: rgba(255, 255, 255, 0.68);
border-radius: var(--radius-md);
overflow: hidden;
}
.tbl th,
.tbl td {
border: 1px solid #ccc;
padding: 6px;
border: 1px solid var(--color-border);
padding: 10px 12px;
text-align: left;
}
.tbl th {
background: rgba(248, 162, 43, 0.12);
color: var(--color-text-secondary);
font-weight: 700;
}
@media (max-width: 760px) {
.vocab-chapter-hero,
.box {
padding: 18px;
}
.grid {
grid-template-columns: 1fr;
}
}
</style>