15 People Frontend Fetching - jpbarbosa/neo4j-crud GitHub Wiki

People Frontend Fetching

Download people images

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)

API

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';

Components

code ./packages/web/src/components/Header.tsx
...

export const Header: React.FC = () => {
  const navItems: NavItem[] = [
    ...
    { path: '/people', label: 'People' },
  ];

  return ...;
};

Pages

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>
  );
};

Test Frontend

open http://localhost:4200
List Search

Commit

git add .
git commit -m "People Frontend Fetching"
⚠️ **GitHub.com Fallback** ⚠️