Enhance usability and localization across components: Update USABILITY_CONCEPT.md with new focus areas, improve user feedback in AppFooter and FamilyView components, and refine text in various UI elements for better clarity and consistency. Replace console logs with user-friendly messages, correct German translations, and streamline interaction logic in multiple components.

This commit is contained in:
Torsten Schulz (local)
2026-03-20 09:41:03 +01:00
parent 1774d7df88
commit c7d33525ff
48 changed files with 1161 additions and 481 deletions

View File

@@ -2,9 +2,9 @@
<div class="diary-view">
<section class="diary-hero surface-card">
<div>
<span class="diary-kicker">Persoenliche Eintraege</span>
<span class="diary-kicker">Persönliche Einträge</span>
<h2>{{ $t('socialnetwork.diary.title') }}</h2>
<p>Gedanken, Notizen und kurze Updates in einer ruhigen, persoenlichen Ansicht.</p>
<p>Gedanken, Notizen und kurze Updates in einer ruhigen, persönlichen Ansicht.</p>
</div>
</section>
@@ -66,14 +66,13 @@ export default {
},
computed: {
...mapGetters(['user']),
},
},
methods: {
sanitizedText(entry) {
return DOMPurify.sanitize(entry.text);
},
async loadDiaryEntries(page) {
try {
console.log(page);
const response = await apiClient.get(`/api/socialnetwork/diary/${page}`);
this.diaryEntries = response.data.entries;
this.currentPage = page;

View File

@@ -4,7 +4,7 @@
<div>
<div class="forum-topic-back link" @click="openForum()">{{ $t('socialnetwork.forum.title') }} {{ forumName }}</div>
<h2 v-if="forumTopic">{{ forumTopic }}</h2>
<p>Diskussionen, Antworten und neue Beitraege in einer fokussierten Leseflaeche.</p>
<p>Diskussionen, Antworten und neue Beiträge in einer fokussierten Lesefläche.</p>
</div>
</section>

View File

@@ -4,10 +4,10 @@
<div>
<span class="forum-kicker">Community-Forum</span>
<h2>{{ $t('socialnetwork.forum.title') }} {{ forumName }}</h2>
<p>Themen, Diskussionen und neue Beitraege an einem strukturierten Ort.</p>
<p>Themen, Diskussionen und neue Beiträge an einem strukturierten Ort.</p>
</div>
<div class="creationtoggler">
<button @click="createNewTopic">
<button @click="toggleCreation">
{{ $t(!inCreation
? 'socialnetwork.forum.showNewTopic'
: 'socialnetwork.forum.hideNewTopic') }}
@@ -16,16 +16,26 @@
</section>
<section v-if="inCreation" class="forum-creation surface-card">
<div class="forum-creation__header">
<div>
<h3>Neues Thema verfassen</h3>
<p>Erst Titel setzen, dann den Beitrag schreiben und anschließend direkt veröffentlichen.</p>
</div>
<button type="button" class="button-secondary" @click="cancelCreation">Abbrechen</button>
</div>
<label class="newtitle">
<span>{{ $t('socialnetwork.forum.topic') }}</span>
<input type="text" v-model="newTitle" />
<input ref="titleInput" type="text" v-model="newTitle" />
</label>
<div class="editor-container">
<EditorContent v-if="editor" :editor="editor" class="editor" />
</div>
<button @click="saveNewTopic">
{{ $t('socialnetwork.forum.createNewTopic') }}
</button>
<div class="forum-creation__actions">
<button :disabled="!canSaveTopic" @click="saveNewTopic">
{{ $t('socialnetwork.forum.createNewTopic') }}
</button>
<span class="forum-creation__hint">Titel und Inhalt müssen beide ausgefüllt sein.</span>
</div>
</section>
<section v-else-if="titles.length > 0" class="forum-topics surface-card">
@@ -47,7 +57,8 @@
</section>
<div v-else class="forum-empty surface-card">
{{ $t('socialnetwork.forum.noTitles') }}
<p>{{ $t('socialnetwork.forum.noTitles') }}</p>
<button type="button" @click="toggleCreation">{{ $t('socialnetwork.forum.createNewTopic') }}</button>
</div>
</div>
</template>
@@ -56,6 +67,7 @@
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import apiClient from '../../utils/axios'
import { showApiError, showSuccess } from '@/utils/feedback.js';
export default {
name: 'ForumView',
@@ -78,6 +90,10 @@ export default {
},
totalPages() {
return Math.ceil(this.numberOfItems / 25)
},
canSaveTopic() {
const content = this.editor ? this.editor.getText().trim() : '';
return this.newTitle.trim().length >= 3 && content.length > 0;
}
},
watch: {
@@ -109,6 +125,24 @@ export default {
if (this.editor) this.editor.destroy()
},
methods: {
focusTitleInput() {
this.$nextTick(() => this.$refs.titleInput?.focus?.());
},
toggleCreation() {
this.inCreation = !this.inCreation;
if (this.inCreation && this.editor) {
this.editor.commands.setContent('');
this.newTitle = '';
this.focusTitleInput();
}
},
cancelCreation() {
this.inCreation = false;
this.newTitle = '';
if (this.editor) {
this.editor.commands.setContent('');
}
},
async loadForum() {
try {
const { data } = await apiClient.get(
@@ -121,16 +155,9 @@ export default {
console.error('Fehler beim Laden des Forums', err)
}
},
createNewTopic() {
this.inCreation = !this.inCreation
if (this.inCreation && this.editor) {
this.editor.commands.setContent('')
this.$nextTick(() => this.editor?.commands.focus('end'))
}
},
async saveNewTopic() {
const content = this.editor ? this.editor.getHTML() : ''
if (!this.newTitle.trim() || !content.trim()) return
if (!this.canSaveTopic) return
try {
const { data } = await apiClient.post(
'/api/forum/topic',
@@ -147,8 +174,11 @@ export default {
this.page = data.page
this.inCreation = false
this.newTitle = ''
this.editor?.commands.setContent('')
showSuccess(this, 'Thema erfolgreich erstellt.')
} catch (err) {
console.error('Fehler beim Erstellen des Themas', err)
showApiError(this, err, 'Fehler beim Erstellen des Themas')
}
},
goToPage(page) {
@@ -211,6 +241,30 @@ export default {
padding: 22px;
}
.forum-creation__header {
display: flex;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
}
.forum-creation__header h3 {
margin: 0 0 6px;
}
.forum-creation__header p,
.forum-creation__hint {
margin: 0;
color: var(--color-text-secondary);
}
.forum-creation__actions {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
.newtitle {
display: flex;
flex-direction: column;
@@ -289,12 +343,20 @@ export default {
text-align: center;
}
.forum-empty p {
margin-bottom: 12px;
}
@media (max-width: 960px) {
.forum-hero {
flex-direction: column;
align-items: flex-start;
}
.forum-creation__header {
flex-direction: column;
}
.topic-card__main {
align-items: flex-start;
}

View File

@@ -2,9 +2,9 @@
<div class="guestbook-view">
<section class="guestbook-hero surface-card">
<div>
<span class="guestbook-kicker">Gaestebuch</span>
<span class="guestbook-kicker">Gästebuch</span>
<h2>{{ $t('socialnetwork.guestbook.title') }}</h2>
<p>Nachrichten, Rueckmeldungen und kleine Einblicke aus deinem Netzwerk.</p>
<p>Nachrichten, Rückmeldungen und kleine Einblicke aus deinem Netzwerk.</p>
</div>
</section>
<div v-if="guestbookEntries.length === 0" class="guestbook-empty surface-card">{{ $t('socialnetwork.profile.guestbook.noEntries') }}

View File

@@ -3,7 +3,7 @@
<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>
<p>Kapitelinhalt durchsuchen, Vokabeln pflegen und direkt in die Übung wechseln.</p>
</section>
<section class="box surface-card">
@@ -269,4 +269,3 @@ export default {
}
}
</style>

View File

@@ -4,7 +4,7 @@
<div>
<span class="vocab-courses-kicker">Kurse</span>
<h2>{{ $t('socialnetwork.vocab.courses.title') }}</h2>
<p>Oeffentliche und eigene Lernkurse filtern, finden und direkt weiterlernen.</p>
<p>Öffentliche und eigene Lernkurse filtern, finden und direkt weiterlernen.</p>
</div>
</section>
@@ -208,12 +208,7 @@ export default {
if (!this.selectedNativeLanguageId) {
this.selectedNativeLanguageId = 'my';
}
console.log(`[loadMyNativeLanguageId] Gefunden: ${nativeLanguageName} (ID: ${nativeLang.id})`);
} else {
console.warn(`[loadMyNativeLanguageId] Sprache "${nativeLanguageName}" nicht in languages-Liste gefunden. Verfügbare Sprachen:`, this.languages.map(l => l.name).join(', '));
}
} else {
console.warn(`[loadMyNativeLanguageId] languages-Liste ist leer.`);
}
} catch (e) {
console.error('Konnte Muttersprache nicht laden:', e);

View File

@@ -97,7 +97,7 @@
<option v-for="chapter in chapters" :key="chapter.id" :value="chapter.id">{{ chapter.title }}</option>
</select>
</div>
<span v-if="lessonFormTouched && !canCreateLesson" class="form-error">Bitte Nummer, Titel und Kapitel vollstaendig angeben.</span>
<span v-if="lessonFormTouched && !canCreateLesson" class="form-error">Bitte Nummer, Titel und Kapitel vollständig angeben.</span>
<div class="form-actions form-actions-row">
<button type="submit" :disabled="!canCreateLesson">{{ $t('general.create') }}</button>
<button type="button" @click="showAddLessonDialog = false" class="button-secondary">{{ $t('general.cancel') }}</button>
@@ -111,7 +111,7 @@
<script>
import { mapGetters } from 'vuex';
import apiClient from '@/utils/axios.js';
import { confirmAction, showApiError, showSuccess } from '@/utils/feedback.js';
import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feedback.js';
export default {
name: 'VocabCourseView',
@@ -259,7 +259,7 @@ export default {
showSuccess(this, 'Lektion erfolgreich angelegt.');
} catch (e) {
console.error('Fehler beim Hinzufügen der Lektion:', e);
showApiError(this, e, 'Fehler beim Hinzufuegen der Lektion');
showApiError(this, e, 'Fehler beim Hinzufügen der Lektion');
}
},
async deleteLesson(lessonId) {
@@ -273,10 +273,10 @@ export default {
try {
await apiClient.delete(`/api/vocab/lessons/${lessonId}`);
await this.loadCourse();
showSuccess(this, 'Lektion erfolgreich geloescht.');
showSuccess(this, 'Lektion erfolgreich gelöscht.');
} catch (e) {
console.error('Fehler beim Löschen der Lektion:', e);
showApiError(this, e, 'Fehler beim Loeschen der Lektion');
showApiError(this, e, 'Fehler beim Löschen der Lektion');
}
},
openLesson(lessonId) {
@@ -285,9 +285,8 @@ export default {
editCourse() {
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/edit`);
},
editLesson(lessonId) {
// TODO: Implement edit lesson
console.log('Edit lesson', lessonId);
editLesson() {
showInfo(this, 'Die Bearbeitung einzelner Lektionen folgt noch.');
}
},
async mounted() {

View File

@@ -6,7 +6,7 @@
<div v-else>
<span class="vocab-language-kicker">Sprache</span>
<h2>{{ $t('socialnetwork.vocab.languageTitle', { name: language?.name || '' }) }}</h2>
<p>Kapitel, Suchfunktionen und Freigaben fuer diese Sprache an einem Ort.</p>
<p>Kapitel, Suchfunktionen und Freigaben für diese Sprache an einem Ort.</p>
</div>
</section>

View File

@@ -10,7 +10,7 @@
<label class="label form-field">
<span>{{ $t('socialnetwork.vocab.languageName') }}</span>
<input v-model="name" type="text" :class="{ 'field-error': nameTouched && !canSave }" />
<span class="form-hint">Ein kurzer, klarer Sprachname reicht fuer den Start.</span>
<span class="form-hint">Ein kurzer, klarer Sprachname reicht für den Start.</span>
<span v-if="nameTouched && !canSave" class="form-error">Der Name sollte mindestens 2 Zeichen haben.</span>
</label>