-
Notifications
You must be signed in to change notification settings - Fork 1
09 Card And Search
JP Barbosa edited this page Apr 15, 2023
·
2 revisions
mkdir -p ./packages/web/public/img/movies
(cd ./packages/web/public/img/movies && curl http://localhost:3333/movies \
| jq '.[] | .title | ascii_downcase' \
| sed "s/['\"]//g" | sed "s/[ \/]/_/g" | sed 's/$/.jpg/' \
| sed "s_^_https://raw.githubusercontent.com/jpbarbosa/neo4j-crud/main/packages/web/public/img/movies/_" \
| xargs -n 1 wget -N)
code ./packages/web/src/utils/fileNameFromString.ts
export const fileNameFromString = (str: string): string => {
return str
.toLocaleLowerCase()
.replaceAll(' ', '_')
.replaceAll('/', '_')
.replaceAll("'", '')
};
code ./packages/web/src/pages/movies/List/Item.tsx
import { Link } from 'react-router-dom';
import { Movie } from '@neo4j-crud/shared';
import { fileNameFromString } from '../../../utils/fileNameFromString';
import { HighlightedText } from '../../../components';
export type ItemProps = {
movie: Movie;
search: string;
};
export const Item: React.FC<ItemProps> = ({ movie, search }) => {
return (
<li key={movie.title}>
<Link to={`${movie.id}/edit`}>
<img
src="/img/px.gif"
style={{
backgroundImage: `url("/img/movies/${fileNameFromString(
movie.title
)}.jpg")`,
}}
alt={movie.title}
/>
<div className="content">
<div>
<h3>
<HighlightedText text={movie.title} search={search} />
</h3>
<div className="released">Released: {movie.released}</div>
<div className="tagline">{movie.tagline}</div>
</div>
<div className="action">
<button className="ghost">Edit</button>
</div>
</div>
</Link>
</li>
);
};
code ./packages/web/src/components/HighlightedText.tsx
type HighlightedTextProps = {
text: string;
search?: string;
};
export const HighlightedText: React.FC<HighlightedTextProps> = ({
text,
search,
}) => {
if (!search) {
return <div>{text}</div>;
}
const regex = new RegExp(search, 'gi');
const highlightedText = text.replace(
regex,
`<span class="highlight">$&</span>`
);
return <div dangerouslySetInnerHTML={{ __html: highlightedText }} />;
};
code ./packages/web/src/components/index.ts
...
export * from './HighlightedText';
code ./packages/web/src/pages/movies/List/index.tsx
import { useState } from 'react';
import { Content } from './Content';
export const List = () => {
const [search, setSearch] = useState<string>('');
return (
<div>
<div className="actions-bar">
<h2>Movies</h2>
<div className="filter">
<input
type="text"
value={search}
placeholder="Search by movie title or person name..."
onChange={(e) => setSearch(e.target.value)}
/>
</div>
</div>
<Content search={search} />
</div>
);
};
code ./packages/web/src/pages/movies/List/Content.tsx
import { useQuery } from 'react-query';
import axios from 'axios';
import { AxiosCustomError, Movie } from '@neo4j-crud/shared';
import { useDebounce } from '../../../hooks/useDebounce';
import { Item } from './Item';
const url = `${import.meta.env.VITE_API_URL}/movies`;
type ContentProps = {
search: string;
};
export const Content: React.FC<ContentProps> = ({ search }) => {
const debouncedSearch = useDebounce(search, 500);
const { data, error, isLoading } = useQuery<Movie[], AxiosCustomError>(
['movies', debouncedSearch],
() => axios.get<Movie[]>(`${url}?search=${search}`).then((res) => res.data)
);
if (error) {
return <div>{error.message}</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
if (!data) {
return <div>No data.</div>;
}
return (
<ul className="record-list">
{data.map((movie) => (
<Item key={movie.id} movie={movie} search={search} />
))}
</ul>
);
};
code ./packages/web/src/hooks/useDebounce.ts
import { useEffect, useState } from 'react';
export const useDebounce = <T>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
open http://localhost:4200
List | Search |
git add .
git commit -m "Card And Search"