Skip to content

Commit

Permalink
feat: Search Users
Browse files Browse the repository at this point in the history
  • Loading branch information
TiagoRibeiro25 committed Aug 28, 2023
1 parent 745a98c commit 7bb73fb
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import Loading from '../components/Loading';
import { UserContext } from '../contextProviders/UserContext';
import ProfileEdit from '../views/ProfileEdit';
import SearchUsers from '../views/SearchUsers';

const AddRecipe = lazy(() => import('../views/AddRecipe'));
const Explore = lazy(() => import('../views/Explore'));
Expand Down Expand Up @@ -54,6 +55,7 @@ const Navigation = () => {
<Route path="/recipes/:tab" element={<Recipes />} />
<Route path="/recipe/:id" element={<Recipe />} />
<Route path="/search" element={<Search />} />
<Route path="/users/search" element={<SearchUsers />} />
<Route path="/user/:id" element={<User />} />
<Route
path="/profile/edit"
Expand Down
67 changes: 67 additions & 0 deletions src/views/SearchUsers/components/UserResult/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { Link } from 'react-router-dom';
import UserNoPicture from '../../../../assets/imgs/user.webp';
import { IUser } from '../../../../types/types';

interface UserResultProps {
user: IUser;
}

const UserResult: React.FC<UserResultProps> = ({ user }) => {
return (
<div className="flex flex-row w-full">
<div className="flex-shrink-0 w-[65px] h-[65px]">
<Link
to={'/user/' + user.id}
className="transition-opacity duration-300 hover:opacity-80"
>
<img
className="object-cover object-center w-16 h-16 rounded-full"
src={user.userImage?.cloudinaryImage ?? UserNoPicture}
alt="user"
loading="lazy"
/>
</Link>
</div>

<div className="flex flex-col w-full pl-3">
<div className="flex flex-col sm:flex-row sm:h-1/2">
<h2 className="text-2xl truncate font-bellefair">
<Link to={'/user/' + user.id} className="hover:underline">
{user.username}
</Link>
</h2>

<div className="flex items-center flex-grow cursor-default sm:justify-end">
<span className="mr-3 text-sm text-gray-600">
<span className="flex-font-bellefair">{user.followers}</span>{' '}
{user.followers === 1 ? 'follower' : 'followers'}
</span>
<span className="text-sm text-gray-600">
<span className="flex-font-bellefair">{user.following}</span> following{' '}
</span>
</div>
</div>

<div className="flex-row hidden sm:flex h-1/2">
<p className="w-full text-sm text-gray-500 truncate">
{user.description.trim().length > 0
? user.description.trim()
: 'No description provided'}
</p>

<p className="flex justify-end flex-grow w-full text-sm text-gray-500">
Joined on{' '}
{new Date(user.createdAt).toLocaleDateString(navigator.language, {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</p>
</div>
</div>
</div>
);
};

export default UserResult;
126 changes: 126 additions & 0 deletions src/views/SearchUsers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import classNames from 'classnames';
import React, { useRef, useState } from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import requests from '../../api/requests';
import SearchIcon from '../../assets/icons/search.svg';
import Input from '../../components/Input';
import Loading from '../../components/Loading';
import NoItemsFound from '../../components/NoItemsFound';
import Reveal from '../../components/Reveal';
import { IUser } from '../../types/types';
import UserResult from './components/UserResult';

const SearchUsers: React.FC = () => {
const [searchInput, setSearchInput] = useState<string>('');
const [users, setUsers] = useState<IUser[]>();
const [loading, setLoading] = useState<boolean>(false);

const searchInputRef = useRef<string>('');
const loadingRef = useRef<boolean>(false);

const fetchUsers = async (searchInput: string): Promise<void> => {
try {
const response = await requests.users.searchUsers({
keyword: searchInput,
limit: 10,
page: 1,
});

if (response.data.success && response.data.data) {
setUsers(response.data.data.users);
}
} catch (error) {
console.log('An error occurred while trying to search for users: ', error);
setUsers(undefined);
}
};

let debounceTimeout: NodeJS.Timeout;

const debounce = (callback: () => void, delay: number) => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(callback, delay);
};

const handleInputChange = async (newValue: string): Promise<void> => {
setSearchInput(newValue);
searchInputRef.current = newValue;

if (newValue.trim().length <= 3) {
setUsers(undefined);
setLoading(false);
loadingRef.current = false;
return;
}

if (loadingRef.current) return;
setLoading(true);
loadingRef.current = true;

debounce(() => {
fetchUsers(newValue).finally(() => {
setLoading(false);
loadingRef.current = false;

// If the value changed while fetching, fetch again
if (newValue !== searchInputRef.current && newValue.trim().length > 3) {
handleInputChange(searchInputRef.current);
}
});
}, 1000);
};

return (
<div
className={classNames(
'flex flex-col items-center pt-12 pb-20 duration-300 ease-in-out max-w-3xl mx-auto mt-28',
searchInput.trim().length > 3 ? 'space-y-8' : 'space-y-20 sm:space-y-32',
)}
>
<Reveal width="100%" animation="fade">
<h1 className="text-3xl text-center font-bellefair">Search for a user</h1>
</Reveal>

<Reveal width="100%" animation="slide-bottom" delay={0.05}>
<div className="flex flex-row items-center py-2">
<LazyLoadImage
className="w-5 h-5 mb-3 mr-3"
src={SearchIcon}
effect="opacity"
alt="Search icon"
/>

<Input
type="search"
id="search"
name="search"
placeholder="Search..."
value={searchInput}
onChange={handleInputChange}
/>
</div>
</Reveal>

{loading && <Loading />}

{users && (
<div className="flex flex-col w-full space-y-8">
{users.map(user => (
<Reveal key={user.id} width="100%" animation="slide-right" delay={0.05}>
<UserResult user={user} />
</Reveal>
))}
</div>
)}

{!loading && !users && searchInput.trim().length > 3 && (
<NoItemsFound
warning="No users found for the given keyword."
message="Try searching for something else."
/>
)}
</div>
);
};

export default SearchUsers;

0 comments on commit 7bb73fb

Please sign in to comment.