feat(news): implement news section with loading state and error handling in NoLoginView
All checks were successful
Deploy to production / deploy (push) Successful in 3m8s
All checks were successful
Deploy to production / deploy (push) Successful in 3m8s
This commit is contained in:
@@ -101,6 +101,28 @@
|
||||
<PasswordResetDialog ref="passwordResetDialog" />
|
||||
</div>
|
||||
|
||||
<section class="public-news surface-card" aria-labelledby="public-news-title">
|
||||
<div class="public-news__header">
|
||||
<div>
|
||||
<span class="panel-kicker">{{ $t('home.nologin.news.kicker') }}</span>
|
||||
<h2 id="public-news-title">{{ $t('home.nologin.news.title') }}</h2>
|
||||
</div>
|
||||
<span v-if="newsLoading" class="public-news__status">{{ $t('home.nologin.news.loading') }}</span>
|
||||
</div>
|
||||
<p v-if="newsError" class="public-news__status">{{ $t('home.nologin.news.unavailable') }}</p>
|
||||
<div v-else-if="news.length" class="public-news__grid">
|
||||
<article v-for="article in news" :key="article.article_id || article.link || article.title" class="public-news__article">
|
||||
<span v-if="article.pubDate" class="public-news__date">{{ formatNewsDate(article.pubDate) }}</span>
|
||||
<a v-if="article.link" :href="article.link" target="_blank" rel="noopener noreferrer" class="public-news__title">
|
||||
{{ article.title }}
|
||||
</a>
|
||||
<h3 v-else class="public-news__title">{{ article.title }}</h3>
|
||||
<p v-if="article.description" class="public-news__description">{{ summarizeNews(article.description) }}</p>
|
||||
</article>
|
||||
</div>
|
||||
<p v-else-if="!newsLoading" class="public-news__status">{{ $t('home.nologin.news.empty') }}</p>
|
||||
</section>
|
||||
|
||||
<section class="seo-content surface-card" aria-label="Sprachtrainer">
|
||||
<h2>{{ $t('home.nologin.languageTrainerSeo.title') }}</h2>
|
||||
<p>
|
||||
@@ -160,6 +182,9 @@ export default {
|
||||
oauthProviders: [],
|
||||
oauthLoading: false,
|
||||
isStoryCollapsed: true,
|
||||
news: [],
|
||||
newsLoading: true,
|
||||
newsError: false,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
@@ -190,6 +215,36 @@ export default {
|
||||
this.oauthProviders = [];
|
||||
}
|
||||
},
|
||||
getNewsLanguage() {
|
||||
const language = String(this.$i18n?.locale || 'de').toLowerCase();
|
||||
return ['de', 'en', 'es', 'fr'].includes(language) ? language : 'en';
|
||||
},
|
||||
async loadNews() {
|
||||
this.newsLoading = true;
|
||||
this.newsError = false;
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/news', {
|
||||
params: { language: this.getNewsLanguage(), category: 'top', count: 3 }
|
||||
});
|
||||
this.news = Array.isArray(data?.results) ? data.results.filter((article) => article?.title) : [];
|
||||
} catch (error) {
|
||||
this.news = [];
|
||||
this.newsError = true;
|
||||
} finally {
|
||||
this.newsLoading = false;
|
||||
}
|
||||
},
|
||||
formatNewsDate(dateStr) {
|
||||
const date = new Date(dateStr);
|
||||
if (Number.isNaN(date.getTime())) return '';
|
||||
return date.toLocaleDateString(this.$i18n?.locale || 'de-DE', {
|
||||
day: 'numeric', month: 'short', year: 'numeric'
|
||||
});
|
||||
},
|
||||
summarizeNews(description) {
|
||||
const text = String(description || '').replace(/\s+/g, ' ').trim();
|
||||
return text.length > 210 ? `${text.slice(0, 207).trimEnd()}…` : text;
|
||||
},
|
||||
startOAuthLogin(providerSlug) {
|
||||
if (this.oauthLoading) {
|
||||
return;
|
||||
@@ -229,7 +284,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadOAuthProviders();
|
||||
await Promise.all([this.loadOAuthProviders(), this.loadNews()]);
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
@@ -554,6 +609,71 @@ export default {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.public-news {
|
||||
width: min(100%, 1120px);
|
||||
margin: 24px auto 0;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.public-news__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.public-news__header .panel-kicker {
|
||||
margin-bottom: 0.45rem;
|
||||
}
|
||||
|
||||
.public-news__header h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.public-news__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.public-news__article {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.public-news__date,
|
||||
.public-news__status {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.public-news__title {
|
||||
margin: 0;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
line-height: 1.35;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.public-news__title:hover {
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.public-news__description {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.seo-content h1 {
|
||||
font-size: 28px;
|
||||
margin: 0 0 8px 0;
|
||||
@@ -626,7 +746,8 @@ export default {
|
||||
.story-columns,
|
||||
.access-split,
|
||||
.login-fields,
|
||||
.oauth-provider-list {
|
||||
.oauth-provider-list,
|
||||
.public-news__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user