8 files added
7 files modified
| | |
| | | "typescript": "~3.7.2" |
| | | }, |
| | | "scripts": { |
| | | "start": "react-scripts start", |
| | | "start": "REACT_APP_NEWS_ENABLED=1 react-scripts start", |
| | | "build": "react-scripts build", |
| | | "test": "react-scripts test", |
| | | "eject": "react-scripts eject", |
| | |
| | | monospace; |
| | | } |
| | | |
| | | .logo { |
| | | height: 2rem; |
| | | } |
| | | |
| | | /* |
| | | .centered { |
| | | text-align: center; |
| | | } |
| | |
| | | animation-name: popupFadeIn; |
| | | animation-duration: .3s; |
| | | } |
| | | */ |
| | |
| | | import { render } from "@testing-library/react"; |
| | | import App from "./App"; |
| | | |
| | | |
| | | test("renders learn react link", () => { |
| | | const { getByText } = render(<App />); |
| | | const linkElement = getByText(/learn react/i); |
| | | const linkElement = getByText(/Adopt a pup/i); |
| | | expect(linkElement).toBeInTheDocument(); |
| | | }); |
| | |
| | | // import ShelterRESTService from "./Services/ShelterRESTService"; |
| | | import AnimalList from "./Components/AnimalList"; |
| | | import SheltersView from "./Views/SheltersView"; |
| | | import NewsView from "./Views/NewsView"; |
| | | import ShelterFakeService from "./Services/ShelterFakeService"; |
| | | import NewsFakeService from "./Services/NewsFakeService"; |
| | | // import ShelterRESTService from "./Services/ShelterRESTService"; |
| | | |
| | | // Services to connect to backends |
| | | |
| | | const shelterService = new ShelterFakeService(); |
| | | // Uncomment to use a real backend |
| | | // const shelterService = new ShelterRESTService(SERVICE_BASE_URL); |
| | | |
| | | const newsService = new NewsFakeService(); |
| | | |
| | | |
| | | |
| | | |
| | | // The main React component that runs the whole webapp |
| | | export default class App extends Component { |
| | | render() { |
| | | const enableNews = process.env.REACT_APP_NEWS_ENABLED; |
| | | return ( |
| | | <Router basename="/frontend"> |
| | | <Switch> |
| | | <Structure> |
| | | <Route path="/" exact > |
| | | Main |
| | | Adopt a pup |
| | | </Route> |
| | | <Route path="/shelters" exact> |
| | | <SheltersView shelterService={shelterService} /> |
| | |
| | | <Route path="/your-animals" exact> |
| | | <AnimalList /> |
| | | </Route> |
| | | {enableNews && |
| | | <Route path="/news" exact> |
| | | <NewsView newsService={newsService} /> |
| | | </Route> |
| | | } |
| | | </Structure> |
| | | </Switch> |
| | | </Router> |
New file |
| | |
| | | import React from "react"; |
| | | import { render } from "@testing-library/react"; |
| | | import AnimalList from "./AnimalList"; |
| | | import NewsFakeService from "../Services/NewsFakeService"; |
| | | |
| | | |
| | | describe("AnimalList", () => { |
| | | |
| | | test("Shows a message before loading results", () => { |
| | | const newsService = new NewsFakeService(); |
| | | |
| | | const { getByText } = render(<AnimalList newsService={newsService} />); |
| | | const linkElement = getByText(/No results found/i); |
| | | expect(linkElement).toBeInTheDocument(); |
| | | }); |
| | | |
| | | test("Shows the loaded results", async() => { |
| | | const newsService = new NewsFakeService(); |
| | | |
| | | const { findByText } = render(<AnimalList newsService={newsService} />); |
| | | const linkElement = await findByText(/News 1/i); |
| | | expect(linkElement).toBeInTheDocument(); |
| | | }); |
| | | |
| | | }); |
| | | |
| | |
| | | |
| | | public render() { |
| | | const { pathname } = window.location; |
| | | const enableNews = process.env.REACT_APP_NEWS_ENABLED; |
| | | return ( |
| | | <Nav theme="dark"> |
| | | <NavList> |
| | |
| | | <NavItem id="your-animals" isActive={pathname.endsWith("/your-animals")}> |
| | | <Link to="/your-animals" >Your Animals</Link> |
| | | </NavItem> |
| | | {enableNews && <NavItem id="news" isActive={pathname.endsWith("/news")}> |
| | | <Link to="/news" >News</Link> |
| | | </NavItem>} |
| | | </NavList> |
| | | </Nav> |
| | | ); |
| | |
| | | |
| | | const Header = ( |
| | | <PageHeader |
| | | logo={<Brand src={imgBrand} alt="Patternfly Logo" />} |
| | | logo={<Brand src={imgBrand} alt="Red Hat Training Logo" className="logo"/>} |
| | | logoProps={logoProps} |
| | | showNavToggle |
| | | isNavOpen={isNavOpen} |
New file |
| | |
| | | import { AnimalService, Animal } from "./AnimalService"; |
| | | |
| | | |
| | | export default class AnimalFakeService implements AnimalService { |
| | | |
| | | public async create(): Promise<void> { |
| | | throw new Error("Method not implemented."); |
| | | } |
| | | |
| | | public async getAllAdoptable(): Promise<Animal[]> { |
| | | return [ |
| | | { |
| | | animalId: "a1", |
| | | animalName: "Dog 1", |
| | | breed: "Shepherd", |
| | | shelterId: "s1", |
| | | adoptable: true |
| | | } |
| | | ]; |
| | | } |
| | | |
| | | public async getById(id: string): Promise<Animal> { |
| | | return { |
| | | animalId: id, |
| | | animalName: "Dog 1", |
| | | breed: "Shepherd", |
| | | shelterId: "s1", |
| | | adoptable: true |
| | | }; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | export interface AnimalService { |
| | | create(): Promise<void>; |
| | | getAllAdoptable(): Promise<Animal[]>; |
| | | getById(id: string): Promise<Animal>; |
| | | } |
| | | |
| | | export interface Animal { |
| | | animalId: string; |
| | | animalName: string; |
| | | shelterId: string; |
| | | breed: string; |
| | | adoptable: boolean; |
| | | } |
| | | |
New file |
| | |
| | | import { NewsService, News } from "./NewsService"; |
| | | |
| | | |
| | | export default class NewsFakeService implements NewsService { |
| | | |
| | | public async getAll(): Promise<News[]> { |
| | | return [ |
| | | { id: "n1", title: "News 1", timestamp: "1970-01-01 00:00:01" }, |
| | | { id: "n2", title: "News 2", timestamp: "1970-01-01 00:00:01" } |
| | | ]; |
| | | } |
| | | } |
New file |
| | |
| | | export interface NewsService { |
| | | getAll(): Promise<any[]>; |
| | | } |
| | | |
| | | export interface News { |
| | | id: string; |
| | | title: string; |
| | | timestamp: string; |
| | | } |
| | | |
New file |
| | |
| | | import React from "react"; |
| | | import { render } from "@testing-library/react"; |
| | | import NewsView from "./NewsView"; |
| | | import NewsFakeService from "../Services/NewsFakeService"; |
| | | |
| | | |
| | | describe("NewsView", () => { |
| | | |
| | | test("Shows a message before loading results", () => { |
| | | const newsService = new NewsFakeService(); |
| | | |
| | | const { getByText } = render(<NewsView newsService={newsService} />); |
| | | const linkElement = getByText(/No results found/i); |
| | | expect(linkElement).toBeInTheDocument(); |
| | | }); |
| | | |
| | | test("Shows the loaded results", async() => { |
| | | const newsService = new NewsFakeService(); |
| | | |
| | | const { findByText } = render(<NewsView newsService={newsService} />); |
| | | const linkElement = await findByText(/News 1/i); |
| | | expect(linkElement).toBeInTheDocument(); |
| | | }); |
| | | |
| | | }); |
| | | |
New file |
| | |
| | | import React from "react"; |
| | | import { Table, TableHeader, TableBody } from "@patternfly/react-table"; |
| | | import { EmptyState, EmptyStateIcon, EmptyStateBody, EmptyStateVariant, Bullseye, Title } from "@patternfly/react-core"; |
| | | import { ErrorCircleOIcon } from "@patternfly/react-icons"; |
| | | import { NewsService, News } from "../Services/NewsService"; |
| | | |
| | | |
| | | type NewsViewProps = { |
| | | newsService: NewsService; |
| | | } |
| | | |
| | | type NewsViewState = { |
| | | news: News[] |
| | | } |
| | | |
| | | |
| | | export default class NewsView extends React.Component<NewsViewProps, NewsViewState> { |
| | | |
| | | constructor(props: NewsViewProps) { |
| | | super(props); |
| | | this.state = { |
| | | news: [] |
| | | }; |
| | | } |
| | | |
| | | public async componentDidMount() { |
| | | const news = await this.props.newsService.getAll(); |
| | | this.setState({ |
| | | news |
| | | }); |
| | | } |
| | | |
| | | public render() { |
| | | return ( |
| | | <Table caption="Latest News" rows={this.getRows()} cells={this.getColumns()}> |
| | | <TableHeader /> |
| | | <TableBody /> |
| | | </Table> |
| | | ); |
| | | } |
| | | |
| | | private getColumns(): [string, string] { |
| | | return ["Timestamp", "Story"]; |
| | | } |
| | | |
| | | private getRows() { |
| | | if (this.state.news.length === 0) { |
| | | return this.getRowsForEmptyTable(); |
| | | } |
| | | return this.state.news.map(this.newsToRow); |
| | | } |
| | | |
| | | private newsToRow(newsItem: News) { |
| | | return { cells: [newsItem.timestamp, newsItem.title] }; |
| | | } |
| | | |
| | | private getRowsForEmptyTable() { |
| | | return [{ |
| | | heightAuto: true, |
| | | cells: [ |
| | | { |
| | | props: { colSpan: 2 }, |
| | | title: ( |
| | | <Bullseye> |
| | | <EmptyState variant={EmptyStateVariant.small}> |
| | | <EmptyStateIcon icon={ErrorCircleOIcon} /> |
| | | <Title headingLevel="h2" size="lg"> |
| | | No results found |
| | | </Title> |
| | | <EmptyStateBody> |
| | | Unable to get news from external feed. |
| | | </EmptyStateBody> |
| | | </EmptyState> |
| | | </Bullseye> |
| | | ) |
| | | }, |
| | | ] |
| | | }]; |
| | | } |
| | | } |
| | |
| | | import { PageSection, PageSectionVariants, Text, TextContent } from "@patternfly/react-core"; |
| | | |
| | | |
| | | type ShelterListProps = { |
| | | type SheltersViewProps = { |
| | | shelterService: ShelterService; |
| | | } |
| | | |
| | | |
| | | export default class SheltersView extends React.Component<ShelterListProps> { |
| | | export default class SheltersView extends React.Component<SheltersViewProps> { |
| | | |
| | | public render() { |
| | | return ( |