Skip to content

Commit

Permalink
Merge pull request #428 from bounswe/3design_frontend_user_search_res…
Browse files Browse the repository at this point in the history
…ults

3design frontend user search results
  • Loading branch information
deniz6221 authored Dec 14, 2024
2 parents a52608a + 16f54fd commit c4abf15
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 78 deletions.
219 changes: 141 additions & 78 deletions 3Design/frontend/src/components/SearchResults/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,142 @@
import React, { useEffect, useState } from 'react'
import styles from "./SearchResults.module.css";
import { useParams } from 'react-router-dom';
import { DPost } from '../interfaces';
import { Skeleton } from 'antd';
import GalleryPost from '../GalleryPost/Clickable/GalleryPost';
import DiscussionPost from '../DiscussionPost/Clickable/DiscussionPost';
import SideBar from '../SideBar/SideBar';
import PageHeader from '../PageHeader/PageHeader';
import axios from 'axios';

const SearchResults = () => {
const {query} = useParams();
const [searchResults, setSearchResults] = useState<DPost[]>([]);
const [searchLoading, setSearchLoading] = useState(true);

if (query == undefined || query == null){
window.location.href = "/home";
}

useEffect(() => {
fetchPostData();
}, [])

const fetchPostData = async () => {
try{
const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/v1/posts?param=${query}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("jwt_token")}`
}
});
setSearchResults(res.data);
}
catch(e){

}
finally{
setSearchLoading(false);
}

}


return (
<>
<PageHeader/>
<div className='flex'>
<SideBar active=""/>
<div className={`flex flex-col gap-4 ${styles.mainContainer} p-4`}>
<div className='flex flex-col gap-6'>
<div>
<p className='font-bold text-xl'>Results for: {query}</p>
</div>
</div>
{ searchLoading ?
(
<div className={styles.spinnerContainer}>
<Skeleton active avatar paragraph={{ rows: 4 }} />
</div>
) :
searchResults.length == 0 ?
<p>No results for this query</p> :
searchResults.map((item, index) => (
item.isVisualPost ?
<GalleryPost key={`g_${item.isVisualPost}`} postData={item}/>
:
<DiscussionPost key={`d_${item.isVisualPost}`} postData={item} />
))
}
</div>

</div>

</>

)
}

import React, { useEffect, useState } from 'react'
import styles from "./SearchResults.module.css";
import { useParams, useSearchParams } from 'react-router-dom';
import { DPost } from '../interfaces';
import { Pagination, Skeleton } from 'antd';
import GalleryPost from '../GalleryPost/Clickable/GalleryPost';
import DiscussionPost from '../DiscussionPost/Clickable/DiscussionPost';
import SideBar from '../SideBar/SideBar';
import PageHeader from '../PageHeader/PageHeader';
import axios from 'axios';
import UserDisplayer from './UserDisplayer/UserDisplayer';

interface UserResponse{
userId : number;
email : string;
nickName : string;
profilePictureUrl : string;
experience : number;
isFollowed : boolean;
}

const SearchResults = () => {
const {query} = useParams();
const [searchResults, setSearchResults] = useState<DPost[]>([]);
const [searchLoading, setSearchLoading] = useState(true);
const [searchType, setSearchType] = useState(true);
const [userResults, setUserResults] = useState<UserResponse[]>([]);

const [searchParams] = useSearchParams();
const passedPageNumber = searchParams.get("p") ?? "";
let pageNumber = 1;
if (/^\d+$/.test(passedPageNumber)){
pageNumber = parseInt(passedPageNumber);
}
const pageSize = 3;

if (query == undefined || query == null){
window.location.href = "/home";
}

useEffect(() => {
fetchData();
}, [searchType])

const fetchData = async () => {
setSearchLoading(true);
if (!searchType){
try{
const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/v1/users/search?keyword=${query}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("jwt_token")}`
}
});
setUserResults(res.data);
}
catch(e){

}
finally{
setSearchLoading(false);
return;
}
}
try{
const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/v1/posts?param=${query}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("jwt_token")}`
}
});
setSearchResults(res.data);
}
catch(e){

}
finally{
setSearchLoading(false);
}

}

const searchTypeHandler = (x: boolean) => {
if (searchType == x){
return;
}
setSearchType(x);
}


return (
<>
<PageHeader/>
<div className='flex'>
<SideBar active=""/>
<div className={`flex flex-col gap-4 ${styles.mainContainer} p-4`}>
<div className='flex flex-col gap-6'>
<div>
<p className='font-bold text-xl'>Results for: {query}</p>
</div>
<div className='flex gap-8 justify-start'>
<button className='btn btn-neutral' style={!searchType ? {background: "#ffffff", color: "black"} : {background: "#d0d0d0", color: "black"}} onClick={() => searchTypeHandler(true)}>Posts</button>
<button className='btn btn-neutral' style={searchType ? {background: "#ffffff", color: "black"} : {background: "#d0d0d0", color: "black"}} onClick={() => searchTypeHandler(false)}>Users</button>
</div>
</div>
{ searchLoading ?
(
<div className={styles.spinnerContainer}>
<Skeleton active avatar paragraph={{ rows: 4 }} />
</div>
) :
searchType ?
(
searchResults.length == 0 ?
<p>No post results for this query</p> :
<>
{searchResults.slice((pageNumber-1)*pageSize, (pageNumber)*pageSize).map((item, index) => (
item.isVisualPost ?
<GalleryPost key={`g_${item.postId}`} postData={item}/>
:
<DiscussionPost key={`d_${item.postId}`} postData={item} />
))
}
<Pagination align='center' pageSize={pageSize} current={pageNumber} total={searchResults.length} onChange={(x)=>window.location.href = `/search/${query}?p=${x}`}/>
</>
) :
(
userResults.length == 0 ?
<p>No user results for this query</p> :
userResults.map((item, index) => (
<UserDisplayer data={item}/>
))
)
}
</div>

</div>

</>

)
}

export default SearchResults
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

.userCard{
background-color: white;
padding: 20px;
width: 100%;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), 0 6px 20px rgba(0, 0, 0, 0.1);
display: flex;
gap: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Avatar } from '@mui/material';
import { Button, message } from 'antd';
import axios from 'axios';
import React, { useState } from 'react'
import styles from "./UserDisplayer.module.css"
interface UserResponse{
userId : number;
email : string;
nickName : string;
profilePictureUrl : string;
experience : number;
isFollowed : boolean;
}

interface Props{
data: UserResponse
}

const UserDisplayer = ({data}: Props) => {
const [stateData, setStateData] = useState<UserResponse>(data);
const [followRequesting, setFollowRequesting] = useState(false);

const handleFollowLogic = async (e:any,type:boolean) => {
e.stopPropagation();
setFollowRequesting(true);
if (type){
try{
await axios.post(`${process.env.REACT_APP_API_URL}/api/v1/users/follow`,
null,
{
headers: {Authorization: `Bearer ${localStorage.getItem("jwt_token")}`},
params: {followedUserId: stateData.userId}
}
);
setStateData(prev => ({...prev, isFollowed: true}));
}
catch(e){
message.error("Couldn't follow user.");
setStateData(prev => ({...prev, isFollowed: false}));
}
finally{
setFollowRequesting(false);
return;
}
}
try{
await axios.delete(`${process.env.REACT_APP_API_URL}/api/v1/users/unfollow`,
{
headers: {Authorization: `Bearer ${localStorage.getItem("jwt_token")}`},
params: {followedUserId: stateData.userId}
}
);
setStateData(prev => ({...prev, isFollowed: false}));
}
catch(e){
message.error("Couldn't unfollow user.");
setStateData(prev => ({...prev, isFollowed: true}));
}
finally{
setFollowRequesting(false);
return;
}
}

return (
<div onClick={() => window.location.href = `/profile/${stateData.userId}`} className={`clickable-post ${styles.userCard}`}>
<div className='flex gap-4 items-center'>
<Avatar src={stateData.profilePictureUrl}/>
<p>{stateData.nickName}</p>
</div>
<div className='mr-0 ml-auto items-center'>

{
stateData.userId != parseInt(localStorage.getItem("user_id") ?? "-1") &&
(stateData.isFollowed ?
<Button disabled={followRequesting} onClick={(e) => handleFollowLogic(e,false)} className='mr-0 ml-auto'>Unfollow</Button> :
<Button disabled={followRequesting} type='primary' onClick={(e) => handleFollowLogic(e,true)} className='mr-0 ml-auto'>Follow</Button>
)
}
</div>
</div>
)
}

export default UserDisplayer

0 comments on commit c4abf15

Please sign in to comment.