Skip to content

Commit 1842dba

Browse files
authored
Merge pull request #214 from rice-crc/develop
Merge develop into main
2 parents 26da28c + 8972483 commit 1842dba

File tree

80 files changed

+2798
-2068
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2798
-2068
lines changed

index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
alt="Matomo Tracking Image"
2121
/> -->
2222
<!-- End Matomo -->
23+
24+
<!-- Google tag (gtag.js) -->
25+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-0455LV1DKZ"></script>
26+
<script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-0455LV1DKZ'); </script>
2327
</head>
2428
<body>
2529
<div id="root"></div>

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/BlogPageComponents/Blogcomponents/BlogCardHeaderBody.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import {
1313
faTwitterSquare,
1414
faLinkedin,
1515
} from '@fortawesome/free-brands-svg-icons';
16+
import defaultImage from '@/assets/voyage-blog.png';
1617
import { faSquareEnvelope } from '@fortawesome/free-solid-svg-icons';
1718
import { useEffect, useRef } from 'react';
1819
import { useDispatch, useSelector } from 'react-redux';
1920
import { Link, useParams } from 'react-router-dom';
2021
import { BASEURL } from '@/share/AUTH_BASEURL';
2122
import { BLOGPAGE } from '@/share/CONST_DATA';
22-
import { convertToSlug } from '@/utils/functions/convertToSlug';
2323

2424
const BlogCardHeaderBody = () => {
2525
const { ID } = useParams();
@@ -33,7 +33,8 @@ const BlogCardHeaderBody = () => {
3333
const effectOnce = useRef(false);
3434
const fetchDataBlog = async () => {
3535
const filters: BlogFilter[] = [];
36-
if ([parseInt(ID!)]) {
36+
const parsedId = parseInt(ID!);
37+
if (!isNaN(parsedId)) {
3738
filters.push({
3839
varName: 'id',
3940
searchTerm: [parseInt(ID!)],
@@ -79,7 +80,7 @@ const BlogCardHeaderBody = () => {
7980
<div className="card-body">
8081
<img
8182
className="blog-detail-thumbnail"
82-
src={`${BASEURL}${thumbnail ? thumbnail : ''}`}
83+
src={thumbnail ? `${BASEURL}${thumbnail}`: defaultImage}
8384
alt={title ? title : ''}
8485
/>
8586
<h1 className="titleText">{title ? title : ''}</h1>
Lines changed: 118 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { useEffect, useRef, useState } from 'react';
2-
1+
import { useCallback, useEffect, useState } from 'react';
32
import { useDispatch, useSelector } from 'react-redux';
43
import { Link } from 'react-router-dom';
54

@@ -25,153 +24,184 @@ import {
2524
import '@/style/blogs.scss';
2625
import { BLOGPAGE } from '@/share/CONST_DATA';
2726

27+
const IMAGES_PER_PAGE = 12;
28+
2829
const BlogResultsList: React.FC = () => {
2930
const dispatch: AppDispatch = useDispatch();
3031
const { blogURL } = usePageRouter();
32+
33+
// Selectors
3134
const {
3235
data: BlogData,
3336
searchAutoKey,
3437
searchAutoValue,
35-
} = useSelector(
36-
(state: RootState) => state.getBlogData as InitialStateBlogProps,
37-
);
38+
} = useSelector((state: RootState) => state.getBlogData as InitialStateBlogProps);
39+
40+
const { languageValue } = useSelector((state: RootState) => state.getLanguages);
41+
const { inputSearchValue } = useSelector((state: RootState) => state.getCommonGlobalSearch);
42+
43+
// Local state
3844
const [totalResultsCount, setTotalResultsCount] = useState(0);
3945
const [page, setPage] = useState<number>(1);
40-
41-
const imagesPerPage = 12;
42-
const { languageValue } = useSelector(
43-
(state: RootState) => state.getLanguages,
44-
);
4546
const [loading, setLoading] = useState(false);
46-
const { inputSearchValue } = useSelector(
47-
(state: RootState) => state.getCommonGlobalSearch,
48-
);
4947

50-
const effectOnce = useRef(false);
51-
const fetchDataBlog = async () => {
52-
setLoading(true);
48+
// Build filters based on current state
49+
const buildFilters = useCallback((): BlogFilter[] => {
5350
const filters: BlogFilter[] = [];
51+
52+
// Add language filter if present
5453
if (languageValue) {
5554
filters.push({
5655
varName: 'language',
5756
searchTerm: [languageValue],
5857
op: 'in',
5958
});
6059
}
61-
if (searchAutoValue || blogURL) {
62-
const convertURL = reverseFormatTextURL(blogURL!);
60+
61+
// Add search filter based on priority: searchAutoValue > blogURL
62+
// If searchAutoValue exists (even if empty string), use it and ignore blogURL
63+
// If searchAutoValue is null/undefined, fall back to blogURL
64+
let searchTerm = null;
65+
66+
if (searchAutoValue !== null && searchAutoValue !== undefined) {
67+
// searchAutoValue is explicitly set (could be empty string or non-empty)
68+
searchTerm = searchAutoValue.trim(); // Use trimmed value, empty string if originally empty
69+
} else if (blogURL) {
70+
// No searchAutoValue set, use blogURL as fallback
71+
searchTerm = reverseFormatTextURL(blogURL);
72+
}
73+
74+
// Only add search filter if we have a non-empty search term and searchAutoKey
75+
if (searchTerm && searchAutoKey) {
6376
filters.push({
6477
varName: searchAutoKey,
65-
searchTerm: [searchAutoValue || convertURL!],
66-
op: 'in',
78+
searchTerm: searchTerm,
79+
op: 'icontains',
6780
});
6881
}
82+
83+
return filters;
84+
}, [languageValue, searchAutoValue, searchAutoKey, blogURL]);
6985

70-
const dataSend: BlogDataPropsRequest = {
71-
filter: filters || [],
72-
page: page,
73-
page_size: imagesPerPage,
74-
};
75-
76-
if (inputSearchValue) {
77-
dataSend['global_search'] = inputSearchValue;
78-
}
79-
86+
// Fetch blog data
87+
const fetchDataBlog = useCallback(async () => {
88+
setLoading(true);
89+
8090
try {
91+
const filters = buildFilters();
92+
const dataSend: BlogDataPropsRequest = {
93+
filter: filters,
94+
page: page,
95+
page_size: IMAGES_PER_PAGE,
96+
};
97+
98+
// Add global search if present
99+
if (inputSearchValue?.trim()) {
100+
dataSend.global_search = inputSearchValue.trim();
101+
}
102+
81103
const response = await dispatch(fetchBlogData(dataSend)).unwrap();
82104

83105
if (response) {
84106
const { results, count } = response;
85-
86107
dispatch(setBlogData(results));
87-
setTotalResultsCount(() => Number(count));
88-
setLoading(false);
108+
setTotalResultsCount(Number(count) || 0);
89109
}
90110
} catch (error) {
111+
console.error('Failed to fetch blog data:', error);
112+
// Optionally dispatch an error action or show error state
113+
dispatch(setBlogData([]));
114+
setTotalResultsCount(0);
115+
} finally {
91116
setLoading(false);
92-
console.log('error', error);
93117
}
94-
};
118+
}, [dispatch, buildFilters, page, inputSearchValue]);
95119

120+
// Reset to first page when filters change
121+
const resetToFirstPage = useCallback(() => {
122+
setPage(1);
123+
}, []);
124+
125+
// Effect to fetch data when dependencies change
126+
useEffect(() => {
127+
fetchDataBlog();
128+
}, [fetchDataBlog]);
129+
130+
// Effect to reset page when filters change (but not page itself)
131+
useEffect(() => {
132+
resetToFirstPage();
133+
}, [languageValue, inputSearchValue, searchAutoKey, searchAutoValue, blogURL, resetToFirstPage]);
134+
135+
// Cleanup on unmount
96136
useEffect(() => {
97-
if (!effectOnce.current) {
98-
fetchDataBlog();
99-
}
100137
return () => {
101138
dispatch(setBlogPost({} as BlogDataProps));
102139
};
103-
}, [
104-
dispatch,
105-
languageValue,
106-
inputSearchValue,
107-
page,
108-
searchAutoKey,
109-
searchAutoValue,
110-
]);
140+
}, [dispatch]);
111141

142+
// Render blog card
143+
const renderBlogCard = (blog: BlogDataProps) => (
144+
<div className="card" key={`${blog.id}-${blog.title}`}>
145+
<Link to={`/${BLOGPAGE}/${formatTextURL(blog.title)}/${blog.id}`}>
146+
<img
147+
src={blog.thumbnail ? `${BASEURL}${blog.thumbnail}` : defaultImage}
148+
alt={blog.title}
149+
className={blog.thumbnail ? "card-img img-fluid content-image" : ""}
150+
style={!blog.thumbnail ? { textAlign: 'center', width: '100%' } : undefined}
151+
/>
152+
<div className="content-details fadeIn-bottom">
153+
<h3 className="content-title">{blog.title}</h3>
154+
{blog.subtitle && <h4 className="content-title">{blog.subtitle}</h4>}
155+
{blog.authors?.map((author, index) => (
156+
<p key={`${author.id}-${author.name}`}>
157+
{index > 0 && ' | '}
158+
{author.name}
159+
</p>
160+
))}
161+
</div>
162+
</Link>
163+
</div>
164+
);
165+
166+
// Loading state
112167
if (loading) {
113168
return (
114169
<div className="loading-logo">
115-
<img src={LOADINGLOGO} alt="loading" />
170+
<img src={LOADINGLOGO} alt="Loading..." />
116171
</div>
117172
);
118173
}
119174

175+
const displayTitle = blogURL ? reverseFormatTextURL(blogURL) : '';
176+
const hasData = BlogData && BlogData.length > 0;
177+
120178
return (
121179
<>
122-
<div id="blog_intro" className="blog_intro">
123-
<h2>{reverseFormatTextURL(blogURL!)}</h2>
124-
</div>
125-
{BlogData.length > 0 ? (
180+
{displayTitle && (
181+
<div id="blog_intro" className="blog_intro">
182+
<h2>{displayTitle}</h2>
183+
</div>
184+
)}
185+
186+
{hasData ? (
126187
<div className={blogURL ? 'container-new-with-intro' : 'container-new'}>
127188
<div className="card-columns">
128-
{BlogData.length > 0 &&
129-
BlogData.map((value) => (
130-
<div className="card" key={`${value.id}${value.title}`}>
131-
<Link
132-
to={`/${BLOGPAGE}/${formatTextURL(value.title)}/${
133-
value.id
134-
}`}
135-
>
136-
{value.thumbnail ? (
137-
<img
138-
src={`${BASEURL}${value.thumbnail}`}
139-
alt={value.title}
140-
className="card-img img-fluid content-image "
141-
/>
142-
) : (
143-
<img
144-
src={defaultImage}
145-
alt={value.title}
146-
style={{ textAlign: 'center', width: '100%' }}
147-
/>
148-
)}
149-
<div className="content-details fadeIn-bottom">
150-
<h3 className="content-title">{value.title}</h3>
151-
<h4 className="content-title">{value.subtitle}</h4>
152-
{value.authors.map((name, index) => (
153-
<p key={`${name.id}${name.name}`}>
154-
{index > 0 && ' | '}
155-
{name.name}
156-
</p>
157-
))}
158-
</div>
159-
</Link>
160-
</div>
161-
))}
189+
{BlogData.map(renderBlogCard)}
162190
</div>
191+
163192
<BlogPageButton
164193
setCurrentBlogPage={setPage}
165194
currentBlogPage={page}
166195
BlogData={BlogData}
167-
imagesPerPage={imagesPerPage}
196+
imagesPerPage={IMAGES_PER_PAGE}
168197
count={totalResultsCount}
169198
/>
170199
</div>
171200
) : (
172-
<NoDataState text={reverseFormatTextURL(blogURL!)} />
201+
<NoDataState text={displayTitle || 'No blogs found'} />
173202
)}
174203
</>
175204
);
176205
};
177-
export default BlogResultsList;
206+
207+
export default BlogResultsList;

0 commit comments

Comments
 (0)