mkdir -p ./packages/web/public/img/people
(cd ./packages/web/public/img/people && curl http://localhost:3333/people \
| jq '.[] | .name | 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/people/_" \
| xargs -n 1 wget -N)
code ./packages/web/src/api/people.ts
import axios from 'axios';
import { Person } from '@neo4j-crud/shared';
const url = `${import.meta.env.VITE_API_URL}/people`;
export const people = {
getAll: (search = '') =>
axios.get<Person[]>(`${url}?search=${search}`).then((res) => res.data),
getById: (id: number) =>
axios.get<Person>(`${url}/${id}`).then((res) => res.data),
};
code ./packages/web/src/api/index.ts
...
export * from './people';
code ./packages/web/src/components/Header.tsx
...
export const Header: React.FC = () => {
const navItems: NavItem[] = [
...
{ path: '/people', label: 'People' },
];
return ...;
};
code ./packages/web/src/app/app.tsx
...
import { ..., People } from '../pages';
export function App() {
return (
<>
<Header />
<Routes>
...
<Route path="/people/*" element={<People />} />
</Routes>
</>
);
}
export default App;
code ./packages/web/src/pages/index.ts
...
export * from './people';
code ./packages/web/src/pages/people/index.tsx
import { Route, Routes } from 'react-router-dom';
import { List } from './List';
export const People = () => {
return (
<Routes>
<Route path="/" element={<List />} />
</Routes>
);
};
code ./packages/web/src/pages/people/List/index.tsx
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { NavigationAlert } from '../../../components';
import { Content } from './Content';
export const List = () => {
const [search, setSearch] = useState<string>('');
return (
<div>
<div className="actions-bar">
<h2>People</h2>
<div className="filter">
<input
type="text"
value={search}
placeholder="Search by person name or movie title..."
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<div>
<Link to="new" className="button primary">
Create Person
</Link>
</div>
</div>
<NavigationAlert />
<Content search={search} />
</div>
);
};
code ./packages/web/src/pages/people/List/Content.tsx
import { useQuery } from 'react-query';
import { AxiosCustomError, Person } from '@neo4j-crud/shared';
import * as api from '../../../api';
import { useDebounce } from '../../../hooks/useDebounce';
import { AlertCombo } from '../../../components';
import { Item } from './Item';
type ContentProps = {
search: string;
};
export const Content: React.FC<ContentProps> = ({ search }) => {
const debouncedSearch = useDebounce(search, 500);
const { data, error, isLoading } = useQuery<Person[], AxiosCustomError>(
['people', debouncedSearch],
() => api.people.getAll(search)
);
const noData = !data || data.length === 0;
if (error || isLoading || noData) {
return <AlertCombo error={error} isLoading={isLoading} noData={noData} />;
}
return (
<ul className="record-list">
{data.map((person) => (
<Item key={person.id} person={person} search={search} />
))}
</ul>
);
};
code ./packages/web/src/pages/people/List/Item.tsx
import { Person, PersonMovie, relationships } from '@neo4j-crud/shared';
import { Link } from 'react-router-dom';
import { fileNameFromString } from '../../../utils/fileNameFromString';
import { HighlightedText } from '../../../components';
import { Movies } from './Movies';
type ItemProps = {
person: Person;
search: string;
};
export const Item: React.FC<ItemProps> = ({ person, search }) => {
return (
<li key={person.name}>
<Link to={`${person.id}/edit`}>
<img
src="/img/px.gif"
style={{
backgroundImage: `url("/img/people/${fileNameFromString(
person.name
)}.jpg")`,
}}
alt={person.name}
/>
<div className="content">
<div>
<h3>
<HighlightedText text={person.name} search={search} />
</h3>
<div className="released">Born: {person.born}</div>
<div className="relationships">
{person.movies !== undefined &&
relationships.map((relationship) => (
<Movies
key={relationship.key}
movies={person.movies as PersonMovie[]}
relationship={relationship}
search={search}
/>
))}
</div>
</div>
<div className="action">
<button className="ghost">Edit</button>
</div>
</div>
</Link>
</li>
);
};
code ./packages/web/src/pages/people/List/Movies.tsx
import {
PersonMovie,
Relationship,
stringToTitleCase,
} from '@neo4j-crud/shared';
import { HighlightedText } from '../../../components';
type MoviesProps = {
relationship: Relationship;
movies: PersonMovie[];
search: string;
};
export const Movies: React.FC<MoviesProps> = ({
relationship,
movies,
search,
}) => {
const filteredMovies = movies.filter(
(movie) => movie.relationship === relationship.key
);
if (filteredMovies.length === 0) {
return null;
}
return (
<div className="mb-8">
<b>{stringToTitleCase(relationship.key)}:</b>
<HighlightedText
text={filteredMovies.map((movie) => movie.title).join(', ')}
search={search}
/>
</div>
);
};
open http://localhost:4200
| List |
Search |
 |
 |
git add .
git commit -m "People Frontend Fetching"