diff --git a/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsElementPage/ui/NewsElementPage.tsx b/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsElementPage/ui/NewsElementPage.tsx
index a7e884914..94dabc3b3 100644
--- a/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsElementPage/ui/NewsElementPage.tsx
+++ b/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsElementPage/ui/NewsElementPage.tsx
@@ -19,6 +19,7 @@ import { linkify } from '@/shared/ui/v2/Chatbot/utils/linkify';
import { useClientTranslation } from '@/shared/i18n';
import { NewsCard } from '@/widgets/NewsCard';
import { ShareButton } from '@/shared/ui/v2/ShareButton';
+import { SkeletonLoaderForNewsElementPage } from '@/shared/ui/SkeletonLoader/ui/SkeletonLoader';
type HeroImageProps = {
picture?: string;
@@ -59,18 +60,65 @@ const NewsElementPage = () => {
const lng = params.lng as string;
const { t } = useClientTranslation('news');
- const { data: moreNews } = useGetNewsQuery({ limit: 2 });
- const { data, isLoading } = useGetNewsByIdQuery(id as string);
+ const { data: moreNews, isLoading: isMoreNewsLoading } = useGetNewsQuery({ limit: 2 });
+ const { data, isLoading: isNewsLoading } = useGetNewsByIdQuery(id as string);
const directusBaseUrl = envHelper.directusHost;
const router = useRouter();
- const lngCode = lng === 'en' ? 'en-US' : lng === 'fi' ? 'fi-FI' : lng;
+
+ const pageIsLoading = isNewsLoading || isMoreNewsLoading || !data || !moreNews;
+
+ const getLngCode = (lng: string) => {
+ if (lng === 'en') return 'en-US';
+ if (lng === 'fi') return 'fi-FI';
+ return lng;
+ };
+
+ const lngCode = getLngCode(lng);
const handleNextNews = (newsId: string) => {
if (newsId) router.push(`/news/${newsId}`);
};
- if (isLoading || !data) {
- return
Loading...
;
+ if (pageIsLoading) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
}
const post = formatNewsSingle(data, lngCode || 'fi-FI');
diff --git a/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsPage.tsx b/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsPage.tsx
index 13ea82efa..1182de4e8 100644
--- a/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsPage.tsx
+++ b/frontend-next-migration/src/preparedPages/NewsPages/ui/NewsPage.tsx
@@ -10,6 +10,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { News } from '@/entities/NewsV2/model/types/types';
import { useClientTranslation } from '@/shared/i18n';
import { PageTitle } from '@/shared/ui/PageTitle';
+import { SkeletonLoaderForNewsPage } from '@/shared/ui/SkeletonLoader/ui/SkeletonLoader';
const NewsPage = () => {
// later use this to fetch data from the backend
@@ -22,7 +23,6 @@ const NewsPage = () => {
const currentSlug = typeof params.slug === 'string' ? params.slug : undefined;
const lngCode = lng === 'en' ? 'en-US' : lng === 'fi' ? 'fi-FI' : lng;
const directusBaseUrl = envHelper.directusHost;
-
const limit = 6;
const [currentPage, setCurrentPage] = useState(1);
const [allNews, setAllNews] = useState([]);
@@ -111,9 +111,11 @@ const NewsPage = () => {
/>
);
})}
- {hasMoreNewsState && }
+
+ {isLoading && }
+
+ {hasMoreNewsState && !isLoading && }
- {isLoading && 'Loading...'}
{renderNoMoreNews()}
diff --git a/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.module.scss b/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.module.scss
index 75a5ad5bd..cd55762a7 100644
--- a/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.module.scss
+++ b/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.module.scss
@@ -151,3 +151,221 @@
width: 100%;
margin-bottom: 8px;
}
+
+/* Skeleton News */
+.newsSkeletonCard {
+ display: grid;
+ grid-template-columns: 1fr;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--base-card-background);
+ border: 3px solid var(--drop-shadows);
+ border-radius: var(--border-radius-figmadesktop);
+ filter: drop-shadow(8px 12px var(--drop-shadows));
+ height: 150px;
+ width: 100%;
+ max-width: 780px;
+ margin: 0 auto;
+ overflow: hidden;
+ position: relative;
+
+ @media (max-width: breakpoint(xl)) {
+ max-width: 500px;
+ }
+
+ @media (max-width: breakpoint(md)) {
+ height: 130px;
+ width: 70%;
+ }
+
+ @media (max-width: breakpoint(sm)) {
+ height: 120px;
+ width: 80%;
+ }
+
+ @media (max-width: breakpoint(xs)),
+ (max-width: 320px) {
+ height: 110px;
+ width: 70%;
+ margin: auto 0;
+ }
+
+ @media (max-width: 320px) {
+ width: 60%;
+ }
+}
+
+.newsSkeletonContent {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ padding: 0.5rem 50% 0 0.5rem;
+ overflow: hidden;
+ position: relative;
+ z-index: 2;
+}
+
+.newsSkeletonImage {
+ position: absolute;
+ right: 0;
+ top: 0;
+ bottom: -50px;
+ width: 70%;
+ height: 100%;
+ background: var(--skeleton-color, #e5e5e5);
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 14% 100%, 0 -22%);
+ transform: translateX(39%);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+ @media (max-width: breakpoint(xs)) {
+ width: 90%;
+ transform: translateX(35%);
+ }
+}
+
+.newsSkeletonTitle {
+ width: 85%;
+ height: 24px;
+ border-radius: 6px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+}
+
+.newsSkeletonText {
+ width: 245px;
+ height: 14px;
+ margin-top: auto;
+ border-radius: 6px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+}
+
+.newsSkeletonTextShort {
+ width: 245px;
+ height: 14px;
+ border-radius: 6px;
+ margin-top: 10px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+}
+
+.newsSkeletonDate {
+ width: 80px;
+ height: 14px;
+ margin-top: auto;
+ margin-bottom: 5px;
+ border-radius: 6px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+}
+
+@keyframes skeletonPulse {
+ 0% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.45;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+/* Skeleton News Element Page */
+.newsElementSkeleton {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ row-gap: 1rem;
+ padding-bottom: 10rem;
+}
+
+.newsElementSkeletonHero {
+ width: 100%;
+ height: 355px;
+ margin-bottom: 0.5rem;
+ border-radius: 4px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+
+ @media (max-width: breakpoint(md)) {
+ height: 250px;
+ }
+
+ @media (max-width: breakpoint(sm)) {
+ height: 190px;
+ }
+}
+
+.newsElementSkeletonTitle {
+ width: 90%;
+ height: 48px;
+ margin: 0 auto 24px;
+ border-radius: 8px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+
+ @media (max-width: breakpoint(sm)) {
+ width: 70%;
+ height: 40px;
+ }
+}
+
+.newsElementSkeletonSubtitle {
+ width: 90%;
+ height: 30px;
+ margin: 0 auto 24px;
+ border-radius: 8px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+
+ @media (max-width: breakpoint(sm)) {
+ width: 85%;
+ height: 26px;
+ }
+}
+
+.newsElementSkeletonMeta {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1rem;
+
+ div {
+ width: 100px;
+ height: 18px;
+ border-radius: 6px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+ }
+}
+
+.newsElementSkeletonBody {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ margin-bottom: 1rem;
+
+ div {
+ width: 85%;
+ height: 20px;
+ border-radius: 6px;
+ background: var(--skeleton-color, #e5e5e5);
+ animation: skeletonPulse 1.5s infinite ease-in-out;
+ }
+
+ .short {
+ width: 55%;
+ }
+
+ @media (max-width: breakpoint(sm)) {
+ div {
+ width: 100%;
+ }
+
+ .short {
+ width: 75%;
+ }
+ }
+}
diff --git a/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.tsx b/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.tsx
index bb051acb8..7ac18276f 100644
--- a/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.tsx
+++ b/frontend-next-migration/src/shared/ui/SkeletonLoader/ui/SkeletonLoader.tsx
@@ -181,3 +181,70 @@ export const SkeletonLoaderWithHeader = ({ sections = 1, className = '' }: Skele
);
};
+
+/**
+ * Renders skeleton cards for the news listing page.
+ * Used while news data is loading to preserve layout and improve UX.
+ *
+ * @param {Object} props - Component props
+ * @param {number} [props.numberOfCards=12] - Number of skeleton cards to render
+ * @param {string} [props.className] - Optional additional CSS classes
+ * @returns {JSX.Element} List of skeleton news cards
+ */
+export const SkeletonLoaderForNewsPage = ({
+ numberOfCards = 12,
+ className = '',
+}: SkeletonLoaderProps) => {
+ const cards = Array(numberOfCards).fill(0);
+
+ return (
+ <>
+ {cards.map((_, index) => (
+
+ ))}
+ >
+ );
+};
+
+/**
+ * Renders a skeleton layout for a single news article page
+ * while the article content is loading.
+ */
+
+export const SkeletonLoaderForNewsElementPage = ({ className = '' }: SkeletonLoaderProps) => {
+ return (
+
+ );
+};